Dans le dernier tutorial, on a vu comment créer un itemRenderer pour les Tweet. Avec quelques hacks pour combler un bug de Flex mais cela fonctionnait quand même:
AIR Mobile – Application TweetDeck (8) – Création d'un itemRenderer pour l'objet Tweet
Avant de continuer, on va essayer de faire pour que notre itemRenderer ait la tête de celui de TweetDeck:

Tout d'abord, on va changer la couleur du fond. Pour cela, on va utiliser une propriété CSS d'IconItemRenderer:"alternatingItemColors". Dans notre cas, les couleurs sont toujours les mêmes, il nous suffit donc de donner une couleur.
Dans le code de l'application principale "TweetDeck.mxml", rajoutez un tag "Style":
<?xml version="1.0" encoding="utf-8"?>
<s:ViewNavigatorApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" firstView="views.TweetDeckHomeView">
<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
s|IconItemRenderer{
alternatingItemColors:#565556;
}
</fx:Style>
</s:ViewNavigatorApplication>
Le rendu:

Pour la couleur du texte de la deuxième ligne, on va utiliser le style CSS "color" qui s'applique au labelDisplay (dans notre cas, en bas):
s|IconItemRenderer{
alternatingItemColors:#565556;
color:#B5B6B5;
}
Maintenant, il nous faut afficher le texte en blanc et pas en noir. En temps normal, il suffirait de rajouter des styles sur IconItemRenderer mais avec notre hack pour l'affichage du texte HTML, on ne peut plus. On va donc indiquer cela dans le contenu HTML. Dans la méthode formatMessage de TweetRenderer.as, remplacez donc la dernière ligne par:
return "<font size=\"" + fontSize + "\" face=\"Arial\" color=\"#FFFFFF\">" + value.replace(urlPattern, "<a href='$1$2$3'>$1$2$3</a>") + "</font>";
Rendu:

Maintenant, les liens doivent aussi être en #B5B6B5. Cela se passe dans la construction de notre StyleSheet dans TweetRenderer.as:
var styles:String = "a {color: #B5B6B5;}";
Rendu:

Maintenant, un petit problème d'ordre esthétique, lorsque les images (icônes) sont en train de charger, elles ne prennent aucune place.
Le framework Flex propose en standard, un composant List, qui réagit aux évènements touch pour une utilisation sur mobile. A cette liste, vous pouvez coupler un renderer comme par exemple un renderer de base de Flex, IconItemRenderer qui vous permet d'afficher une image + 2 lignes de texte pour chaque élément.
Mais il y a un autre type de rendu assez populaire sur mobile, qui est de créer une liste à "sections". Par exemple la liste de contacts sous Android ou iOS. Les contacts sont arrangés par ordre alphabétique et rassemblés par la première lettre de leur prénom:

Les sections ne sont pas cliquables, elles servent juste de décoration. Elles ont le plus souvent une taille différente de celle des items. Je ne suis pas un cador du développement natif mais je pense qu'il y a un composant tout prêt pour ce genre d'utilisation. En Flex, il vous faudra le faire vous-même.
Mais comme d'habitude, certains se sont posé la question avant vous (désolé) et on trouvé une solution. Voici quelques liens qui vous seront utiles:
http://corlan.org/2011/07/04/creating-flex-mobile-section-lists/
http://corlan.org/2011/07/11/creating-flex-mobile-lists-part-ii-using-virtualization/
http://www.tink.ws/blog/flex-4-excludeselectionlist/
http://code.google.com/p/tink/source/browse/trunk/flex4.5/spark/src/ws/tink/spark/controls/ExcludeSelectionList.as
http://flexponential.com/2011/06/27/extending-labelitemrenderer-to-look-like-itunes-on-the-ipad/
Voilà, je vous ai bien préparé le travail mais on va aller plus loin en regardant les techniques utilisées.
La technique générale
Pour intégrer ces sections, et que le rendu de la liste soit cohérent, on intègre ces sections directement dans le dataProvider de la liste. Au milieu de vos objets Contact, vous aurez donc d'autres items qui sont là pour la déco. Il faut ensuite déterminer dans votre liste quels sont ces éléments et leur donner un rendu différent.
Le composant ExcluseSelectionList
Le (très bon) blogger de tink.ws propose une implémentation de List.as (par héritage) qui permet d'indiquer au composant quels éléments sont "non-sélectionnables". Vous disposez donc de plusieurs propriétés pour définir cette restriction:
- excludeType / excludeTypes : Permet de donner une liste de type AS3 à exclure (ex: la classe Header)
- excludeIndex / excludeIndices : Permet de donner une liste d'index à exclure (ex 1, 10, 30)
- excludeFunction : Permet de passer une fonction qui va renvoyer un Boolean pour indiquer s'il faut exclure ou pas l'élément. Un peu comme une filterFunction.
Ce composant va se révéler très pratique mais on peut voir que l'itemRenderer créé par flexponential.com contient un petit "trick" qui permet de se passer de cette restriction. En effet, il se contente de ne pas passer l'itemRenderer en état "sélectionné" s'il est sur un header. Un couplage des 2 solutions semble le meilleur compromis.
Utilisation des itemRenderer
C'est la solution la plus naturelle et la plus élégante d'un point de vue code. Suivant l'item passé à l'itemRenderer, on va modifier le rendu.On peut faire ça facilement en MXML mais il vaut mieux le faire en AS3 pour avoir de meilleurs perfs sur mobile.
L'exemple de flexponential explique bien la technique:
http://flexponential.com/2011/06/27/extending-labelitemrenderer-to-look-like-itunes-on-the-ipad/
Ici, on utilise bien la propriété itemRenderer et pas une itemRendererFunction. C'est encore mieux car l'utilisation d'une itemRendererFunction empêche la ré-utilisation des renderers et devient donc moins performante en RAM. Dans l'exemple, le composant MusicLabelItemRenderer est taillé pour cette application puisqu'il connait le type d'item qu'il va afficher. Il peut donc facilement faire la distinction entre un item et un header. Très pratique si vous connaissez votre liste, moins si vous voulez être générique.

En l’occurrence, le renderer est assez complexe puisqu'il permet d'afficher les éléments de manière horizontale avec une ligne séparatrice.
La problématiques des layouts
Voici un problème que je n'avais même pas envisagé pendant la rédaction de cet article. De base, une List utilise un "layout" qui est un VerticalLayout. On obtient donc une liste verticale. Mais sur de plus grands écrans, vous voudrez peut-être utiliser un layout qui vous permet d'afficher plusieurs éléments par ligne, comme un TileLayout:


Si vous modifier votre itemRenderer, vous n'aurez pas un header sur une ligne complète, juste sur la taille d'un item. Le résultat sera donc mauvais. Les billets de corlan.org peuvent vous permettre de résoudre cette problématique en faisant en sorte que votre header prenne toute la largeur. C'est un cas assez exceptionnel mais au moins vous êtes prévenus
.
Conclusion
Avec ces différents articles, vous avez assez d'informations pour pouvoir créer votre liste. Certaines subtilités comme le layout ou la manière par laquelle on rend les éléments non-sélectionnables sont optionnels, essayez de faire au plus simple.
Dans un futur tutorial, je vais peut-être partager un exemple complet de liste avec groupement avec en bonus le composant de scroll rapide comme sur Android. Pour ceux qui ne voient pas, c'est une liste alphabétique qui vient au dessus de la liste, permettant d'aller directement à la bonne lettre. Très pratique quand on a des dizaines / centaines d'éléments
.
Dans un des billets précédents, on a vu quels étaient les nouveaux composants Flex 4.5:
AIR Mobile – Les composants et le thème Mobile Flex 4.5
Mais il y a aussi d'autres composants qui sont un peu à part qui sont les itemRenderer. Comme vous le savez peut-être déjà, ce que l'on appelle un "itemRenderer" dans le langage Flex est un composant qui va être utilisé au sein d'une liste. Pour une liste, on a des lignes de données. Pour chaque ligne de données, on va devoir effectuer le rendu d'un point de vue graphique de cette ligne. Le rendu le plus basique est celui d'afficher un texte, tout simple.
Mais vous voudrez parfois représenter des données plus complexes, avec plusieurs champs ou des composants graphiques différents comme des images par exemple. Vous allez alors créer votre propre composant qui va afficher les données de votre liste comme vous le souhaitez, c'est ce que l'on appelle un "itemRenderer".
Utiliser les itemRenderer de Flex 4.5?
Il est donc possible de créer vos propres itemRenderer ou d'utiliser ceux prévus dans le SDK. Alors pourquoi utiliser ceux du SDK?
Et bien les itemRenderer du SDK comme LabelItemRenderer et IconItemRenderer sont optimisés à l’extrême pour un affichage le plus rapide possible. Les performances que vous aurez avec un IconItemRenderer seront toujours meilleurs que si vous crééz le votre avec 2 labels et une image. Les optimisations sont faîtes notamment sur les points suivants:
- Cache sur les images + délai d'affichage lors du scrolling
- Gestion des DPI pour les espacements
- Rendu des textes optimisé par l'utilisation du composant StyleableTextField
- Gestion des invalidations
C'est donc toute cette logique que vous n'aurez pas à coder. Mais si votre itemRenderer ne correspond pas à un itemRenderer pré-mâché du SDK, vous avez toujours la possibilité de créer le votre. S'il n'y a que des modifications mineures, je vous conseille cependant d'utiliser l'héritage pour ajouter vos modifications. Si vous partez vraiment from scratch, n'hésitez pas à vous inspirer du code des classes de Flex qui contiennent les meilleurs techniques d'optimisation.
Utilisation du LabelItemRenderer
Voici l'itemRenderer le plus simple, il va vous permettre d'afficher un Label.
Un petit exemple d'utilisation:
<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="HomeView">
<fx:Declarations>
<s:ArrayCollection id="ac">
<fx:Array>
<fx:String>Ligne 1</fx:String>
<fx:String>Ligne 2</fx:String>
<fx:String>Ligne 3</fx:String>
<fx:String>Ligne 4</fx:String>
<fx:String>Ligne 5</fx:String>
<fx:String>Ligne 6</fx:String>
<fx:String>Ligne 7</fx:String>
<fx:String>Ligne 8</fx:String>
</fx:Array>
</s:ArrayCollection>
</fx:Declarations>
<s:List width="100%" height="100%" dataProvider="{ac}">
<s:itemRenderer>
<fx:Component>
<s:LabelItemRenderer />
</fx:Component>
</s:itemRenderer>
</s:List>
</s:View>
Et voilà le rendu dans le simulateur:

Pas impressionnant du tout mais les performances sont excellentes
.
Utilisation du composant IconItemRenderer
IconItemRenderer est un composant qui hérite de LabelItemRenderer, vous permettant d'aller bien plus loin dans l'affichage et dans la personnalisation.
La création de composants est un must quand on travaille avec Flex. Mais autant faut-il savoir les créer de la meilleures manière afin qu'ils puissent être ré-utilisés et faciliter la maintenance de votre code.
A ce propos, Tom Sugden a écrit un très bon article qui explique bien les meilleures manières pour faire un bon composant Flex:
Writing Genuinely Reusable Flex Components
Pour faire simple, je vais traduire cet article, et rajouter quelques commentaires et suggestions…
Pour de gros projets, en entreprise, on se retrouve souvent à extraire un ensemble de composants réutilisables dans un projet Flex Library. En théorie, les mêmes composants peuvent être utilisés dans plusieurs modules et/ou dans des applications Flex / Air. Cela permet d'avoir une base de code solide et facile à maintenir pour un développement plus rapide.
Cependant, dans la pratique, certaines erreurs de conception sont faites, qui limitent la ré-utilisabilité des composants. Cet article explique comment créer un composant totalement ré-utilisable et met en avant certaines techniques du SDK Flex qui peuvent être appliquées à vos propres composants.
Comment définir un composant "vraiment" réutilisable?
Il y a différents niveaux de ré-utilisabilité, mais un composant ré-utilisable devrait toujours pouvoir faire le rendu de n'importe quel type de donnée. Il devrait pouvoir fonctionner avec un tableau d'objets basiques (type Object) ou une collection d'objet de type Kangaroo par exemple.
Le composant Flex DataGrid permet de faire cela:
<mx:DataGrid dataProvider="{ kangaroos }">
<mx:columns>
<mx:DataGridColumn headerText="Name" dataField="name"/>
<mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
</mx:columns>
</mx:DataGrid>
Notez que les propriétés "dataField" et "labelFunction" indiquent au composant comment récupérer sa donnée depuis les objets de type Kangaroo, sans imposer de dépendance. On a ici deux mécanismes possibles pour rendre un composant vraiment ré-utilisable. Même si le développeur n'as pas le contrôle sur la classe Kangaroo (si elle fait partie d'une librairie externe par exemple), il peuvent tout de même être rendu dans une DataGrid.
L'Anti-Pattern Data Interface
Une des erreurs courantes est d'obliger la donnée qui va être rendue par un composant à implémenter une interface spécifique. Par exemple, on va prendre le composant DistributionBar qui fait le rendu d'un graphique comme celui-ci:

On affiche un certain nombre de régions de tailles différentes, chacune contenant un label. Il est tentant de configurer ce composant pour qu'il utilise un tableau d'objet IRegion:
public interface IRegion
{
function get label() : String;
function get size() : int;
}
Le composant pourra ensuite extraire les informations de taille et de label pour chaque région par cette interface. L'argument en faveur de cette démarche est que l'interface découple le composant de l'objet concret qui est rendu. N'importe quel objet peut être rendu tant qu'il implémente IRegion, mais c'est dans le "tant que" que réside la faille de conception.
En imposant l'utilisation de l'interface IRegion, la ré-utilisabilité du composant est limitée. L'interface doit être implémentée par les classes du Model avant de pouvoir être rendue par le composant. Encore pire, si vos models sont produits par une autre librairie (dont vous pouvez ne pas avoir les sources) ou par une autre équipe, il se peut que vous ne puissiez pas changer les classes, elles devront donc être "wrappées" (encapsulées dans d'autres objets qui implémentent IRegion).
Pour ces raisons, le composant n'est pas vraiment ré-utilisable.
Notez que cette considération est importante pour les composants qui doivent être complètement ré-utilisables comme les DataGrid ou les List Flex. Pour des composants plus "métiers" ou le fait de pouvoir instancier son composant sans configurer trop de propriétés, on pourra utiliser le "pattern Data Interface". Parfois, il vaut mieux passer du temps à créer un composant qui soit ré-utilisable mais parfois il est plus aisé/rapide de créer plusieurs composants (par héritage) pour des besoins métiers spécifiques.
Les composants ré-utilisables du SDK Flex
Le SDK Flex contient de nombreux composants ré-utilisables, utilisant plusieurs approches:
- Data Fields
- Data Functions
- Data Descriptor
- Factory Objects
Ces approches et ces techniques peuvent être appliquées à vos propres composants pour les rendre ré-utilisables.
Traduction de l'article Differences between Flex 3 and Flex 4 beta de Joan Lafferty.
Modifications apportées sur les Effect en Flex 4
De nombreuses améliorations ont été faites sur l'architecture des Effect dans Flex 4. Les effets Halo (Flex 3) ne fonctionnaient que sur les composants basés sur UIComponent. Les effets Spark (Flex 4) peuvent agir sur n'importe quelle cible, même les primitives graphiques du framework. Tous ces effets se trouvent dans le package spark.effects.*.
Puisque ces effets fonctionnent sur les composants Halo, les composants Spark et les primitives graphiques, Adobe recommande l'utilisation des effets Spark pour toutes vos futures applications.
Voir l'article de Chet Hasse sur Effects in Adobe Flex 4 SDK beta
Modifications apportées sur le Layout (mise en page)
Dans les versions précédentes de Flex, la mise en page des composants et des conteneurs était définie dans chaque composant. C'est pourquoi on avait des composants tels que List, TileList et HorizontalList, qui apportaient tous les mêmes fonctionnalités sauf que leur mise en page était différente.
Dans Flex 4, la mise en page a été découplée des composants. Maintenant, les composants Spark tels que Application, List, ButtonBar et Panel peuvent définir leur mise en page de manière déclarative. Dans tous les composants, cela est géré par la classe Group et la mise en page des enfants de Group est laissé à un objet "layout" associé. Cette mise en page supporte à la fois les composants Spark et Halo en plus des primitives graphiques FXG. La mise en page peut même être changée à l'exécution.
En tant que développeur, vous pourrez facilement écrire des "layout" personnalisées et basculer de layout directement dans votre composant. Voici un exemple qui définit une liste verticale, une liste horizontale et une tiled list (en grille):
Vertical List (layout par défaut)
<s:List />
Horizontal List
<s:List>
<s:layout>
<s:HorizontalLayout />
</s:layout>
</s:List>
Tiled List
<s:List>
<s:layout>
<s:TileLayout />
</s:layout>
</s:List>