Flex4 : composant personnalisé et skinning


backcompo

Je vous propose un petit tour d’horizon de la création de compo personnalisés avec Flex4 et le skinning. Vous allez voir ! Comme moi, vous ne voudrez plus jamais faire de Flex3. Moi ça fait 5 mois… et j’en suis bien content !

Alors voila. Nous allons voir comment faire un composant personnalisé avec le SDK4, de la conception actionScript, au skinning.
Il me fallait une idée de départ… Ceux qui me suivent sur Twitter aurons vu/lu qu’en ce moment je m’éclate avec Eaze, la librairie d’animation de Philippe Elsass. Super légère, facile d’utilisation et performante (Nicoptère a réalisé quelques “petites” démos sur le sujet). Je ne saurais que trop vous la conseiller.
Et bien on va se faire un petit compo de visualisation de ces fonctions d’animations :)

Première étapes : la primitive étendue

Avant de partir sur le composant, nous allons d’abord faire une extension de StrokedElement, qui va se charger de dessiner les courbes d’animations.

La classe StrokedElement est, dans Flex4, la classe de base pour tous les éléments graphiques ayant à dessiner une ligne. FilledElement, extensions de StrokedElement, s’occupe du remplissage. Les classes Line, Rect, Ellipse, Path… sont elles mêmes des extensions de FilledElement.

Nous allons donc créer une classe EazeElement, qui étend StrokedElement.
L’idée ici n’est pas d’afficher la “vraie” courbe d’accélération, mais plutôt d’avoir une “information visuelle” qui nous donne une idée sur l’évolution de l’animation, avec un point de départ et d’arrivé identique… Le voir en vrai c’est plus simple qu’à expliquer…

Nous allons associer à cette primitive 2 propriétés : eazeFunction : la fonction d’animation a tracer, et step la “précision” de la ligne.

Ensuite, il suffit d’overrider la méthode draw, et de faire ce que l’on veut dedans.
En gros nous allons boucler sur la largeur de l’élément, en se décalant à chaque passe de la valeur step, et calculer la position x, y de notre ligne en fonction de eazeFunction…. Vous pouvez voir en vrai la classe sous ce lien.

Aller hop on test tout ça ! Pour l’apparence, nous ajoutons à notre élément un “IStroke”. Cela peut être, <s:SolidColorStroke />, <s:LinearGradientStroke />, … Y’en à d’autres : à vous de tester !

<primitives:EazeElement
    eazeFunction="Elastic.easeInOut" step="2"
    horizontalCenter="0" verticalCenter="0" width="400" height="200">

    <primitives:stroke>

        <s:LinearGradientStroke rotation="90">
            <s:GradientEntry color="#F19000" />
            <s:GradientEntry color="#00FF00" />
            <s:GradientEntry color="#009EE0" />
        </s:LinearGradientStroke>

    </primitives:stroke>

</primitives:EazeElement>

eaze element
Rhooo ! C’est jouli !

C’est parti pour le composant

Notre composant va étendre SkinnableContainer, base pour la création de composant.

package com.lafabrick.components
{
    public class SimpleEazeViewer extends SkinnableContainer
    ...

Nous voulons utiliser notre primitive EazeElement dans ce compo. Il pourra aussi être redimensionnable, via une “ancre”. Nous devons donc définir ces différents objets de contrôles, dans la définition de notre composant. Nous utilisons ici les SkinPart.

SkinPart

Une skinPart définit un objet, visuel ou non, devant être présent (de manière obligatoire ou non) dans la skin. Ajoutons nos parties :

public class SimpleEazeViewer extends SkinnableContainer
{
    [SkinPart(required="false")]
    public var anchor : Group;

    [SkinPart(required="true")]
    public var eazeElement : EazeElement;

ici, eazeElement est requis et obligatoire. anchor, l’ancre pour redimensionner le composant, par contre est optionnel.

Nous verrons plus bas comment les utiliser dans le composant. Occupons nous des états de ce composant.

SkinState

SkinnableContainer définit 2 états de base : “normal” et “disabled”. Ces états devront être présents dans la skin. Nous allons ajouter un état “selected” : lorsque l’utilisateur déplacera l’ancre, le composant passera en état “selected”. Pour ajouter un état personnalisé, dans l’en-tête de classe, ajoutons le tag suivant :

[SkinState(name="selected")]

Nous allons également créer une propriété selected, qui définira si le composant est sélectionné…

dans le setter, nous allons appeler la fonction invalidateSkinState(), qui va déclencher le processus de changement d’état. Pour relier la propriété selected à l’état sélectionner, nous devons overrider la fonction protégée getCurrentSkinState().

private var _selected : Boolean = false;

public function get selected():Boolean
{
    return _selected;
}

public function set selected(value:Boolean):void
{
    _selected = value;
    invalidateSkinState();
}

override protected function getCurrentSkinState() : String
{
    return selected ? "selected" : "normal";
}

Ajoutons enfin la dernière propriété eazeFonction : Function qui va nous permettre de définir la fonction à tracer par la primitive.

Overridons un brin

Il est temps de passer à la gestion de tout ça. Ce que l’on a besoin, c’est de savoir quand les différents éléments définit dans les SkinPart sont ajoutés, pour leur pousser les propriétés et autres écouteurs qui vont bien. Pour cela, nous devons overrider la méthode partAdded de la manière suivante.

override protected function partAdded(partName:String, instance:Object) : void
{
    super.partAdded( partName, instance );

    if( instance == eazeElement ) {
        eazeElement.eazeFunction = eazeFunction;
    }

    if( instance == anchor ) {
        anchor.addEventListener(MouseEvent.MOUSE_DOWN, onAnchorMouseDown );
        systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, onMouseUp );
        systemManager.getSandboxRoot().addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove );
    }
}

Dans le sens inverse, il faut overrider partRemoved pour, entre autre chose, enlever les écouteurs sur les éléments.

override protected function partRemoved(partName:String, instance:Object) : void
{
    super.partRemoved( partName, instance );

    if( instance == anchor ) {
        anchor.removeEventListener(MouseEvent.MOUSE_DOWN, onAnchorMouseDown );
        systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_UP, onMouseUp );
        systemManager.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove );
    }
}

Plus qu’à mettre la mécanique dans les fonctions qui vont bien.

La classe complète ici

plutôt super simple ce fonctionnement !

Côté skin

Skin ou SparkSkin ?

Vous avez 2 possibilités : partir de Skin ou SparkSkin, la dernière étant une extension de la première. La différence est que SparkSkin ajoute une mécanique spéciale pour exclure des éléments de la mécanique de skinning. Dans Flex4 vous pouvez injecter des couleurs de base à vos skin, avec la propriété baseColor. L’exclusion permet d’éviter de coloriser les éléments sélectionnés. Je vous invite a faire un saut dans les classes Skin des boutons (ex: spark.skins.spark.ToggleButtonSkin ) pour voir à quoi cela ressemble.

Nous allons choisir Skin.

Pointer sur le composant cible

En premier lieu, nous devons spécifier quel est le composant cible de cette skin. Cela se passe dans le tag metadata :

<fx:Metadata>
    [HostComponent("com.lafabrick.components.SimpleEazeViewer")]
</fx:Metadata>

Les états

Nous devons aussi retrouver les états définit dans le composants : normal, disabled et selected :

<s:states>
    <s:State name="normal" />
    <s:State name="selected" />
    <s:State name="disabled" />
</s:states>

Plus qu’a ajouter vos éléments définit dans les skinPart (un Group avec l’id “anchor” et un EazeElement avec l’id “eazeElement” ). Et puis vous amusez a faire de la déco à grand coup de Rect, Ellipse, Line, Path, …. La grande classe !
Tout ces éléments se calibrent avec les contraintes top, left, bottom, right. Les possibilités sont infinies.

<s:Group id="anchor" top="0" bottom="0" width="16" right="0">
    <s:Rect
        topRightRadiusX="8" topRightRadiusY="8"
        bottomRightRadiusX="8" bottomRightRadiusY="8"
        top="0" bottom="0" left="0" right="0">
        <s:fill>
            <s:SolidColor color="#212121" alpha="0" />
        </s:fill>
    </s:Rect>

    <s:Group verticalCenter="0" height="16" horizontalCenter="0">
        <s:Line top="0" bottom="0" horizontalCenter="-2">
            <s:stroke>
                <s:SolidColorStroke color="#353535" />
            </s:stroke>
        </s:Line>
        <s:Line top="0" bottom="0" horizontalCenter="0">
            <s:stroke>
                <s:SolidColorStroke color="#353535" />
            </s:stroke>
        </s:Line>
        <s:Line top="0" bottom="0" horizontalCenter="2">
            <s:stroke>
                <s:SolidColorStroke color="#353535" />
            </s:stroke>
        </s:Line>
    </s:Group>	

</s:Group>

Pimp my State! inclusion, exclusion, groupage….

Voyons comment gérer notre skin en fonction de ses états.
Le nouveau système de gestion d’états est bien loin de celui proposé dans Flex3. Plus simple et bien plus logique, il vous permet un contrôle total de votre skin.
Admettons que vous ayez un composant avec les états suivants :

<s:states>

    <s:State name="up" />
    <s:State name="down" />
    <s:State name="over" />
    <s:State name="upAndSelected" />
    <s:State name="downAndSelected" />
    <s:State name="overAndSelected" />
    <s:State name="disabled" />

</s:states>

Un rectangle est définit dans la skin, et doit être inclus dans les états « up », « down », « over ». La déclaration de l’inclusion, et des différentes couleurs en fonction des états devra se faire suivant cette exemple :

<s:Rect includeIn="over, down, up" top="0" bottom="0" left="0" right="0">
    <s:stroke>
        <s:SolidColorStroke color="#212121" />
    </s:stroke>

    <s:fill>
        <s:LinearGradient rotation="90">
            <s:GradientEntry color.down="#FCD301" color.over="#FCD301" color="#F7F7F7" ratio="0" />
            <s:GradientEntry color.down="#F19000" color.over="#F19000" color="#C2C6C7" ratio="1" />
        </s:LinearGradient>
    </s:fill>

    <s:filters>
        <s:DropShadowFilter color="#000000" distance="1"
            blurX.over="10" blurX.down="10" blurX="4"
            blurY.over="10" blurY.down="10" blurY="4"
            quality="3" alpha="0.8" />
    </s:filters>
</s:Rect>

Vous pouvez voir ici que l’attribution de valeur en fonction des états fonctionne aussi bien sur des couleurs que sur n’importe quelle autre propriété, comme sur le filtre.

Les états proposent une mécanique de groupage par nom, qui vous permet de pousser encore plus loin la gestion de la skin. Un état peut faire partie de plusieurs groupes :

<s:states>

    <s:State name="up" />
    <s:State name="down" stateGroups="mouseOn" />
    <s:State name="over" stateGroups="mouseOn" />
    <s:State name="upAndSelected"  stateGroups="selected, mouseOn" />
    <s:State name="downAndSelected"  stateGroups="selected, mouseOn" />
    <s:State name="overAndSelected" stateGroups="selected, mouseOn" />
    <s:State name="disabled" />

</s:states>

Les états down et over, en vue « normal » et « sélectionné » font partie d’un même groupe « mouseOn ». De même, les états sélectionnés sont regroupés dans un groupe « selected ».

Nous pouvons alors modifier la skin de notre rectangle. Au lieu de définir dans quel état le rectangle est présent, nous allons plutôt lui définir dans quels groupes d’états il est exclu :

<s:Rect excludeFrom="selected" top="0" bottom="0" left="0" right="0">

De même, la définition des couleurs et des propriétés du filtre peut être affectée globalement en fonction du groupe d’état :

<s:Rect excludeFrom="selected" top="0" bottom="0" left="0" right="0">
    <s:stroke>
        <s:SolidColorStroke color="#212121" color.mouseOn="#F7F7F7" weight.mouseOn="2" />
    </s:stroke>

    <s:fill>
        <s:LinearGradient rotation="90">
            <s:GradientEntry color.mouseOn="#FCD301" color="#F7F7F7" ratio="0" />
            <s:GradientEntry color.mouseOn="#F19000" color="#C2C6C7" ratio="1" />
        </s:LinearGradient>
    </s:fill>

    <s:filters>
        <s:DropShadowFilter color="#000000" distance="1"
            blurX.mouseOn="10" blurX="4"
            blurY.mouseOn="10" blurY="4"
            quality="3" alpha="0.8" />
    </s:filters>
</s:Rect>

Le rectangle est exclu des états sélectionnés, et les couleurs sont attribuées en fonction du groupe définissant une interaction de l’utilisateur « mouseOn ».

Sympa non?

Je veux mon composant !

Vous pouvez bien sur accéder a votre composant via hostComponent : si par exemple vous définissez un texte dans votre composant, vous avez possibilité de l’affecter directement dans votre skin comme ceci :

<s:Label id="labelDisplay"

    text="{hostComponent.monTitre}"

    textAlign="center"
    verticalAlign="middle"
    maxDisplayedLines="1"
    horizontalCenter="0" verticalCenter="1"
    left="10" right="10" top="2" bottom="2" />

Bon a savoir : le contentGroup

SkinnableContainer définit un Group optionnel, contentGroup, qui, comme son nom l’indique est le groupe du contenu :) Ce groupe est le réceptacle des différents éléments que vous pouvez ajouter a votre compo. Par exemple un Panel dispose de ce contentGroup. Sinon on met rien dedans et ce ne serait plus un Panel…. Bon nous n’en avons pas besoin ici, mais si jamais vous avez besoin d’un composant qui peut être aussi un réceptacle de composants, n’oublier pas le contentGroup !

Retournons à notre compo…

… et regardons la skin complète.

Un peu plus loin…

Imaginons maintenant que vous ne vouliez plus afficher une courbe mais plusieurs à la suite, définit dans un tableau. Vous n’aurez donc plus un eazeElement, mais un Group qui contiendra vos primitives.
Le problème alors est de pouvoir skinner le trait de vos primitives. Vous devrez donc définir un IStroke. Ce qui est intéressant dans ce nouveau système de skin, c’est qu’il est aussi possible de définir ce Stroke, pourtant élément non visuel, dans une SkinPart.

[SkinPart(required="false")]
public var anchor : Group;

[SkinPart(required="true")]
public var eazeGroup : Group;

[SkinPart(required="true")]
public var eazeStroke : IStroke;

La déclaration côté Skin ce fera a l’intérieur des balises déclaration :

<fx:Metadata>
    [HostComponent("com.lafabrick.components.MultipleEazeViewer")]
</fx:Metadata>

<fx:Declarations>
    <s:LinearGradientStroke id="eazeStroke" rotation="90">
        <s:GradientEntry color="#F19000" />
        <s:GradientEntry color="#00FF00" />
        <s:GradientEntry color="#009EE0" />
    </s:LinearGradientStroke>
</fx:Declarations>

La nouvelle classe, se cache derrière ce lien, et la skin associée derrière celui-ci.

compos final

La démo finale ( les sources )

J’espère que comme moi vous tomberez sous le charme. C’est fou comme une évolution peut vous montrer à quel point c’était tout nul avant !


Tags: , , , ,


8 commentaires ...

» RSS des commentaires
  1. switcherdav /
    -->

    Salut,

    Excellent article.

    Une question sur la librairie Eaze, quel est le gain par rapport à TweenLite ? le poids ?

    Bon ben maintenant va falloir se mettre à Flex 4 alors …..

  2. fab /
    -->

    Indéniablement le poids ! Mais aussi la facilité de prise en main, les performances, et cette syntaxe, façon JQuery, que je trouve intéressante. Je pense que cette lib peut en intéresser plus d’un. Et gagner des Ko, c’est quand même pas mal quand on fait du Flex :)

  3. Joe /
    -->

    Salut,
    Excellent tuto qui donne envie de faire du Flex tiens…
    Petite question par curiosité, avec quel soft as-tu créé tes skins. A la main ? Illustrator ? Firewoks ?
    Autre petite question, vu que tu testes Flex4 depuis pas mal de temps. A part ce nouveau skinning et les transitions animées, les améliorations de layout et le TLF, y a t il d’autres nouveautés + ou – marquantes dans Flex 4 qui améliore la vie du développeur ?
    Car avec ce nouveau skinning, je me demande si on va enfin pouvoir faire des vrai site avec Flex et pas seulement des admins ou autres applis AIR…

  4. fab /
    -->

    @joe pour la skin : a la mano. Pour du skinning plus complexes je jette souvent mes idées, plus ou moins évoluées, sur Fireworks. Jamais Illustrator ou Photoshop : c’est le mal ! Fireworks est pour moi le parfait compagnon de l’UI-Designer. Par contre je recompose tous à la main, après un bref export FXG pour les formes et Path complexes : c’est le meilleur moyen pour moi d’obtenir la qualité et la finesse recherché.

    Dans la liste de changement que tu donnes j’ajouterais l’architecture des nouveaux composants Spark. Et quand tu dis « les améliorations de layout », je dirais pluôt révolution ! A par quelques subtilités, tu as listé les plus importantes concernant le SDK : il t’en faut plus? :) Et en terme de simplification de vie, il y a la dose d’évolution dans l’IDE FlashBuilder.

    Par contre le « je me demande si on va enfin pouvoir faire des vrai site avec Flex et pas seulement des admins ou autres applis AIR » c’est super réducteur là… Quand tu dis « site », je dis « passe ton chemin » ! On est quand même sur un principe de RIA/RDA (à la base), pas pour faire un petit portfolio de communication. Le but c’est quand même de faire de l’applicatif qui soit fonctionnel, ergonomique, et joli. J’ai vu pas mal d’applis vraiment énormes avec Flex. En France pas grand chose… Alors si ton point de comparaison reste sur l’hexagone, à la rigueur. Par contre si le job c’est faire du web promotionnel (je comprend comme ca « vrai site » :) ), Flash CS c’est très bien.

    Et pour revenir aux évols, je suis d’accord sur le fait que le SDK3 a/avait beaucoup de faiblesse, mais les changements et nouveautés sont tellement importants pour moi, qu’il faudrait presque considérer cette version 4 comme étant autre chose qu’une simple « évolution ».

  5. Quentin /
    -->

    Bien ouèj, bonne entrée en matière… Et en plus les composants sont jolis !
    J’ai un peu joué avec la bête aussi (http://toki-woki.net/blog/p1156-filewatch) mais je ne suis pas forcément aussi enthousiaste que toi concernant la nouvelle logique de skin.

    Effectivement ça ouvre beaucoup de possibilités et ça facilite grandement les choses quand on veut faire un composant très différent de ceux existants, mais faire des choses simples peut être un peu lourd. L’exemple d’un bouton avec icône par exemple est parlant, vu qu’il n’y pas vraiment de notion d’héritage pour les skins on est obligé de repartir à zéro (Adobe conseille même de faire un copier/coller des sources !), ce qui me semble dommage…

    J’ai aussi un peu été déçu par la façon de rendre un composant paramétrable (via du CSS), je n’ai d’ailleurs pas encore trouvé la solution miracle !

    Un avis sur la/les question(s) ?
    ps : Une petite checkbox pour être averti des nouveaux commentaires (http://txfx.net/wordpress-plugins/subscribe-to-comments/) serait un plus !

  6. fab /
    -->

    @quentin Effectivement l’histoire de l’icon dans les boutons… ba c’est pas d’office ! mais « c’est pour faire ce qu’on veut mon enfant » ! Copier/coller les sources… c’est reloud, mais on peut faire mieux ! le top étant une « librairie » perso pour la skin.

    Que vaut il mieux : un cascade d’héritage à la Flex3, qui engendre des processus complexe? ou la simplicité de Fx4? Moi je préfère de loin l’architecture Fx4. La liberté est bien plus grande, et la qualité est clairement au rendez-vous. Le cas des List est le super exemple : côté Fx3, une grosse bouillie infâme, difficilement skinnable, à la gestion horrible, ans parler de ces #@!~$ d’itemRenderer… côté Fx4, un dataGroup, un layout une skin. J’exagère mais on en ai vraiment pas loin !

    Pour le reste, dans Fx4, il suffit de créer 2/3 classes de skin bien réutilisable, via CSS par exemple, et plus besoin de copier/coller quoi que ce soit. Pour les css c’est en fait très simple, il suffit de passer par le hostComponent dans la skin : hostComponent.getStyle(« maSuperPropriétéCSS »);

    Je prépare d’autre tutos sur le sujet, et p’têtre un pack de démarrage pour nouvelle skin : y’aura plus à la faire comme ca ! :D

    Tiens d’ailleurs, si vous voulez des tutos particulier sur le sujet (ou non), faites signe !

  7. Quentin /
    -->

    Complètement d’accord, Flex 4 est plus puissant/personnalisable et oui on est content de dire au revoir aux itemRenderers…

    Merci pour le getStyle(), assez simple ! La solution miracle était en fait de faire comme pour Flex 3…

    Bonne idée le « Skin Starter Pack », je vote pour !

  8. Antonin /
    -->

    Excellent mec !

    Merci pour ce bien beau tuto, ca tombe pile poil bien pour mon projet RIA :)


Sois pas timide...


Bad Behavior has blocked 263 access attempts in the last 7 days.