Flex 4 et les layouts – Faire une ViewStack « pure » Spark

Pour ceux d’entre vous qui ont eu la chance de passer à Flex 4, vous avez peut-être la même tendance que moi à chercher à éviter le préfixe <mx:> dans vos skins… Spark, quand tu nous tiens… Quoi qu’il en soit, certains types de composants n’ont pas été réécris pour Spark. Pourquoi ? Parce l’architecture Spark propose quelque chose de top : les layouts. Et les layouts, c’est vaaaachement bien.
Explication…
Layouts
Sans faire un article complet sur les layout (ce qui serait tout à fait possible et intéressant), on peut tout de même rappeler que le Flex 4 se distingue de Flex 3 via Spark par une volonté de mieux séparer le fond de la forme, il amène donc une notion de layout pour, comme son nom le suggère, organiser les composants d’un conteneur (par exemple un <s:Group>).
Exit donc la VBox ou la HBox (même si le VGroup ou le HGroup permettent de faire une transition en douceur), et bienvenue au Group avec layout :
<mx:VBox> </mx:VBox>
devient donc
<s:Group> <s:layout> <s:VerticalLayout /> </s:layout> </s:Group>
Au premier abord on dit : c’est plus verbeux ! Oui mais… le layout a cette particularité, en tant que propriété du composant, d’être modifiable à la volée, contrairement à un composant imposant sa mise en forme. Concrètement, là où la VBox présentera toujours ses enfants verticalement (c’est son nom !), le Group pourra changer à volonté de layout, donc très facilement passer de HorizontalLayout à VerticalLayout par exemple… On comprend encore mieux l’intérêt quand on se met à écrire son propre layout pour faire des choses un peu plus poussées visuellement !
Créer son layout
Pour créer un layout, il faut étendre la classe LayoutBase et surcharger a minima la méthode updateDisplayList() et certainement la méthode measure(). Ces méthodes gardent le même rôle qu’en Flex 3, mais avec l’adaptation contextuelle au Layout :
- updateDisplayList : dispose les enfants du conteneur et définit leur taille
- measure : donne la taille globale du conteneur
Vous pouvez accéder à la propriété « target » du layout qui correspond au conteneur auquel est appliqué le layout et travailler sur les enfant via les méthodes getElementAt() ou getVirtualElementAt(). La seconde méthode sert dans le cas où le layout est dit « virtualisé ».
Apparté : virtualisation de layout
Flex 4 permet, pour des raison de performance, d’utiliser un layout en mode « virtualisé », c’est-à-dire ne calculant la disposition des seuls enfants à afficher et non l’ensemble des enfants. Ce mode de fonctionnement s’applique particulièrement bien aux composants comprenant une liste de beaucoup d’enfants, mais ne calculant l’affichage d’une poignée à la fois, le reste attendant un scroll ou une action similaire pour être éventuellement affiché.
Application : création d’un ViewStackLayout
Avec les layouts en tête, il est inutile de demander pourquoi le composant ViewStack n’existe pas dans Spark : il devrait en pratique être un layout applicable à n’importe quel conteneur ! Même si le très bon article sur la virtualisation mentionne un prometteur je cite « (Planned) StackLayout », rien ne nous empêche d’en faire un simple, adapté à l’architecture Spark, plutôt que d’attendre (oui on est impatients à la fabrick).
Voici un exemple simple :
import mx.core.IVisualElement;
import spark.layouts.supportClasses.LayoutBase;
public class ViewStackLayout extends LayoutBase {
public function ViewStackLayout() {
super();
}
protected var _index:uint;
public function get index():Number {
return _index;
}
public function set index(value:Number):void {
if (_index != value && target != null && value >= 0 && value < target.numElements) {
_index = value;
target.invalidateSize();
target.invalidateDisplayList();
}
}
override public function updateDisplayList(width:Number, height:Number):void {
var element:IVisualElement = useVirtualLayout ? target.getVirtualElementAt(index) : target.getElementAt(index);
if (element) {
element.setLayoutBoundsSize(element.getPreferredBoundsWidth(), element.getPreferredBoundsHeight());
target.setActualSize(element.getPreferredBoundsWidth(), element.getPreferredBoundsHeight());
target.setContentSize(element.getPreferredBoundsWidth(), element.getPreferredBoundsHeight());
}
}
override public function measure():void {
var count:int = target.numElements;
for (var i:uint = 0; i < count; i++) {
var element:IVisualElement = useVirtualLayout ? target.getVirtualElementAt(i) : target.getElementAt(i);
if (i == index) {
element.visible = true;
element.includeInLayout = true;
target.measuredWidth = element.getPreferredBoundsWidth();
target.measuredHeight = element.getPreferredBoundsHeight();
} else {
element.visible = false;
element.includeInLayout = false;
}
}
}
}
Vous noterez ici l’usage du getVirtualElementAt, en prévision de la gestion de la virtualisation.
Vous pouvez donc utiliser ce layout dans un DataGroup « piloté » par une tabbar par exemple comme ceci :
<s:TabBar id="tabEditors" />
<s:DataGroup id="editors">
<s:layout>
<mylayouts:ViewStackLayout index="{tabEditors.selectedIndex}" />
</s:layout>
<!-- Enfants... -->
</s:DataGroup>
De cette façon, le jour où vous voudrez présenter ces éléments sous forme de CoverFlow, en mode TimeMachine… vous n’aurez pas à modifier le code de votre composant, mais uniquement votre skin.
Alors, on n’est pas mieux habillé en Spark ?
Tags: architecture, AS3, Flex, flex4, MVC, Spark, UI Design

--> 15 juillet 2010 ( 15:03 )
Ce qui aurait été awesome ça aurait été de pouvoir développer un système de Factory pour rajouter cette histoire d’index dans n’importe quel layout déjà existant, ça évite de le surcharger pour ajouter ces 2 méthodes.
J’avoue que je ne vois pas comment faire ça proprement, la tout de suite dans ma tête j’ai une histoire de composition et de surcharge de toutes les méthodes de la classe LayoutBase… A méditer!
Merci pour l’article en tout cas, je n’avais pas pensé à cette utilisation des Layout!
--> 15 juillet 2010 ( 15:20 )
Ouep, je comprends ta frustration, en même temps un layout de base ne gère pas d’index vu qu’il affiche tous ses enfants… On n’a pas particulièrement le choix, et il faut bien déclencher la mise à jour lorsque l’index en question est mis à jour.
On pourra toujours imaginer une classe abstraite (même si ça n’existe pas en AS 3) qui implémente par défaut un index et ce déclenchement pour que tous les layout approchant une viewstack en terme de fonctionnement puisse hériter de cette classe de base.
Allez, soyons fous, appelons-la IndexedLayoutBase !
--> 18 juillet 2010 ( 15:06 )
Bonjour,
merci pour l’article
une question: pourquoi le get est-il Bindable?
une proposition suite au mot de Palleas: on ne peut pas mettre en attribut le Layout existant dans ViewStackLayout ?
--> 18 juillet 2010 ( 23:02 )
Pour le Bindable, c’est une trace que j’ai oublié de supprimer. Ca doit fonctionner sans.
Pour ta proposition, j’ai du mal à comprendre où tu veux en venir ? Qu’apporterait la présence d’un attribut layout dans le ViewStackLayout ? Un layout ne peut pas avoir lui-même de layout… Ou alors j’ai rien compris à ta suggestion ?
Pour clarifier un peu, c’est justement une propriété de ce layout que d’avoir l’index de l’enfant en cours d’affichage.
Le layout étant en théorie applicable à n’importe quel conteneur, il doit être générique et demander à la skin de lui fournir les éléments dont il a besoin pour gérer correctement son affichage. Un jour l’index viendra d’une tabbar, un autre jour d’autre part. Si par exemple ton composant venait définir lui-même l’index du ViewStackLayout, on perdrait également la généricité inhérente aux layouts, un changement de skin provoquant un plantage du composant.
Mais je reste ouvert à la discussion !
--> 18 janvier 2011 ( 16:25 )
J’ai eu des problèmes de layout avec l’utilisation de ViewStackLayout, je l’ai modifié en le faisant hériter de spark.layouts.VerticalLayout au lieu de spark.layouts.supportClasses.LayoutBase en prenant soin d’appeler les méthodes « mères » dans les méthodes surchargées !
Works like a charm !
--> 18 janvier 2011 ( 21:23 )
Impeccable ! Good job
J’ai effectué quelques retouches pour gérer correctement les tailles notamment. Reste à valider la bonne gestion de la taille max du conteneur par rapport aux tailles souhaitées des enfants.