Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox

Flex4 et les modèles de mise en page personnalisés (layout)

Compatible Flex 4. Cliquer pour en savoir plus sur les compatibilités.Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par autourdeflash (Gilles Doucen - http://autourdeflash.wordpress.com/), le 29 mars 2011
  • Révision : le 30/03/2011 à 21H45 par autourdeflash

Pré-requis

Une première expérience en développement Flex vous sera nécessaire pour profiter de ce tutoriel. Des notions sur les itemRenderers, dataProvider, dataGroup sont notamment demandées. Reportez-vous à la section tutoriels si nécessaire.

Présentation

Flex 4 introduit un nouveau modèle de composant nommé Spark  destiné à remplacer à terme le jeu de composants Halo (mx) introduit avec flex3. Il repose sur une nouvelle architecture séparant logique et vue. Le processus de skinnage est facilité d'autant plus que vous pouvez le réaliser directement depuis le logiciel Flash Catalyst en seulement quelques minutes.

Spark s'appuie également sur un nouveau moteur de mise en page. il permet d'assigner par composition un nouveau modèle de mise en page à un conteneur Spark. C'est sur ce dernier point que portera précisement ce tutoriel.

Les modèles de bases

Avec les conteneurs Halo, la position et les dimensions des éléments graphiques sont gérés et définis au sein même du conteneur par le biais de propriétés et de styles spécifiques. Spark apporte un niveau d'abstraction entre le conteneur et le modèle de mise en page tout en permettant à ce dernier d'être défini séparément des skins et des styles. Cette délégation apporte une grande flexibilité et permet notamment de changer efficacement de mise en page dynamiquement. Au final, comportement, vue et mise en page du composant se trouvent séparées dans des classes différentes.

Par défaut, 4 modèles de mise en page sont disponibles:

  • BasicLayout, reprend le modèle de mise en page adopté par le conteneur de type Canvas (librairie Halo). Les éléments sont positionnés dans l'espace par le biais des propriétés x,y,z.
  • HorizontalLayout, reprend le modèle de mise en page adopté par le conteneur Hbox (librairie Halo). Les éléments sont alignés horizontalement de la gauche vers la droite.
  • VerticalLayout, reprend le modèle de mise en page adopté par le conteneur Vbox (librairie Halo). Les éléments sont alignés verticalement du haut vers le bas.
  • TileLayout, reprend le modèle de mise en page adopté par le conteneur Tile (librairie Halo). Les éléments sont répartis sur une ou plusieurs colonnes verticales ou lignes horizontales selon la valeur de la propriété orientation qui détermine la direction de la mise en page. La ligne est le choix par défaut.

Ils héritent tous de la classe LayoutBase qui leur fournit le comportement de base.

Associer un layout à un conteneur

Prenons comme exemple un conteneur de type Group. Le fonctionnement sera toujours le même quelque soit le type de conteneur, ce que nous vérifierons plus bas en utilisant un conteneur de type DataGroup.

<s:Group id="group1">
    <s:Button label="btn1" />
    <s:Button label="btn2" />
</s:Group>

le conteneur a pour identifiant « group1 » et est composé de 2 boutons. Par défaut, son layout est de type BasicLayout. Pour l'instant, les 2 boutons occupent la même position à l'intérieur du conteneur et se superposent avec des coordonnées x et y nulle. Je veux que son contenu soit aligné sur une même colonne verticale et que les éléments soient tous séparés de 10 pixels. Pour ce faire on doit créer une instance du modèle de mise en page de type VerticalLayout et l'associer a notre conteneur.

Il existe plusieurs manières de le faire.

en as3,

var vLayout:VerticalLayout = new VerticalLayout(); // on crée une instance
vLayout.gap = 10; // on définit l'interstice de 10 pixels
group1.layout = vLayout; // on l'associe au conteneur via la propriété layout

en Mxml,

si on choisit une déclaration en ligne,

<s:Group id="group1">
	<s:layout>
		<s:VerticalLayout gap="10"/>
	</s:layout>
	<s:Button label="btn1"/>
	<s:Button label="btn2" />
</s:Group>

ou si on choisit une déclaration plus souple via la balise <fx:Declarations> (qui contient la déclaration des éléments non graphiques) et une connexion de type DataBinding,

<fx:Declarations>
    <s:VerticalLayout id="vlayout5" gap="10" />
</fx:Declarations>
 
<s:Group id="group1" layout="{vlayout5}" >
    <s:Button label="btn1"/>
    <s:Button label="btn2" />
</s:Group>

changer de modèle de mise en page dynamiquement

Mettons cela en images.

Les sources des exemples présentés dans cet article sont accessibles via le menu contextuel et l'option View Source.

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Le code source correspondant est listé ci-dessous (vous pouvez le télécharger en bas de la page):

<fx:Script>
	<![CDATA[
		...
		[Bindable]
		private var layouts : ArrayCollection;
 
	        private function applicationCompleteListener(event : FlexEvent) : void {
			layouts = new ArrayCollection();
			layouts.addItem({label:'Vertical', data:new VerticalLayout()});
			layouts.addItem({label:'Horizontal', data:new HorizontalLayout()});
			layouts.addItem({label:'Basic', data:new BasicLayout()});
 
			var tileLayout:TileLayout = new TileLayout();
			tileLayout.orientation = 'columns';
			layouts.addItem({label:'Grille', data:tileLayout});
 
			setDataSource();
		}
                ...
 
	]]>
</fx:Script>
 
<s:HGroup>
	<s:DropDownList
		id="layoutList"
		requireSelection="true"
		width="190"
		dataProvider="{layouts}" />
        ...
</s:HGroup>
 
<s:DataGroup 
	width="100%" height="100%"
	layout="{layoutList.selectedItem.data}"
	dataProvider="{dataSource}"
	itemRenderer="com.autourdeflash.flex4.demos.customlayouts.renderers.SimpleRenderer">
</s:DataGroup>

Dans cette exemple, vous disposez d'une boîte de sélection (DropDownList) qui vous permet de changer de modèle de mise en page dynamiquement et qui montre bien que les modèles sont entièrement découplés du conteneur lui même. Chaque modèle est décrit dans un objet comprenant son label sous la propriété du même nom et une instance sous la balise data. C'est cette dernière propriété qui est connectée par DataBinding à la propriété layout du conteneur DataGroup. Les changements de sélection opérés dans la boîte de sélection sont automatiquement répercutés sur la propriété layout du conteneur et donc sur sa mise en page.

On remarque que lorsque l'on applique directement le modèle BasicLayout, la position des éléments reste inchangée. Cela provient tout simplement qu'avec ce modèle, aucune transformation n'est appliquée par défaut. Chaque élément conserve donc son ancienne position.

Parmi les différents paramètres que l'on peut retrouver pour chaque modèle, revient l'attribut useVirtualLayout qui définit la notion de virtualisation que l'on peut ou non activer. Rapidement, qu'est ce que cela signifie ? Cette notion consiste à limiter le nombre d'instances d'itemRenderer créés au nombre d'items visibles à l'écran. Ces mêmes itemRenderers sont ensuite « recyclés » pour permette d'afficher la pile entière de données. Cela permet d'augmenter significativement les performances de rendu et n'a d'effet que sur les conteneurs qui supportent cette fonction c'est à dire à ma connaissance uniquement les dataGroup pour l'instant.

Modèle personnalisé

Revenons à nos modèles de mise en page. Si vous désirez créer votre propre modèle, vous devez créer une classe héritant de la classe LayoutBase et surcharger au minimum la méthode updateDisplayList. A l'intérieur de cette méthode, vous pouvez accéder aux éléments graphiques enfants du conteneur et leur appliquer les transformations 2D ou 3D de votre choix. Cela peut suffire si le contenu doit s'adapter aux dimensions du conteneur. Dans le cas inverse, c'est à dire si le conteneur doit s'adapter au contenu, on devra également surcharger la méthode measure.

Dans l'exemple qui suit, on retrouve 2 modèles de mise en page:

  • CircularLayout: distribution radiale dans le sens des aiguilles d'une montre
  • RandomLayout: distribution aléatoire à l'intérieur du conteneur

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

L'application reprend la même structure que l'exemple précédent à l'exception près que les modèles de mise en page sont cette fois-ci décrits au format mxml sous la balise <fx:Declarations>. Reportez-vous aux codes sources en bas de la page pour plus de détails.

Attardons-nous maintenant sur la classe CircularLayout qui décrit le modèle de distribution radiale.

package com.autourdeflash.flex4.demos.customlayouts.layouts {
	import spark.components.supportClasses.GroupBase;
	import spark.layouts.supportClasses.LayoutBase;
 
	import mx.core.ILayoutElement;
 
	public class CircularLayout extends LayoutBase {
 
		private var padding:Number = 25;
 
		override public function updateDisplayList(width:Number, height:Number):void {
			super.updateDisplayList(width, height);
 
			var container:GroupBase = target as GroupBase;
 
			var count:uint = container.numElements;
			var w:Number = width*0.5;
			var h:Number = height*0.5;
			var angle:Number = 360/count;
			var radius:Number = Math.min( w, h ) - padding;;
			var radians:Number;
			var xpos:Number;
			var ypos:Number;
			var element:ILayoutElement;
 
			for( var i:int = 0; i < count; i++ )
			{
				element = container.getElementAt( i ); // on part du principe que l'on active pas la virtualisation
 
				radians = ( -i*angle + 180 ) * ( Math.PI / 180 );
 
				xpos = w + ( Math.sin(radians) * radius );
				ypos = h + ( Math.cos(radians) * radius );
 
				element.setLayoutBoundsSize( NaN, NaN ); // spécifie que les dimensions d'affichage de l'élément sont définis par l'élément lui même
				element.setLayoutBoundsPosition( xpos, ypos );
			}
		}
	}
}

Dans notre cas, la position des éléments est déterminée à partir des limites du conteneur parent, si bien que l'on peut se contenter de surcharger uniquement la méthode updateDisplayList. Cette méthode est appelée lorsque le conteneur est redimensionné ou que la liste des éléments enfants est modifiée.

On obtient une référence sur le conteneur auquel est appliqué ce modèle via la variable target qui hérite de la classe GroupBase.

var container:GroupBase = target as GroupBase;

Le nombre d'éléments graphiques enfants contenus est résolu à partir de la variable numElements.

var count:uint = container.numElements;

Les éléments graphiques implémentent l'interface IlayoutElement.

var element:ILayoutElement;

On parcourt la liste des éléments graphiques. Chaque élément est résolu à partir de son index à l'aide de la méthode getElementAt ou getVirtualElementAt si la virtualisation est activée.

for( var i:int = 0; i < count; i++ )
{
  element = container.getElementAt( i );
  ...
}

On redéfinit leur position et dimensions respectivement via les méthodes setLayoutBoundsPosition et setLayoutBoundsSize. En précisant les valeurs NaN comme paramètres de la méthode setLayoutBoundsSize, on spécifie que les dimensions d'affichage de l'élément sont définis par l'élément lui même.

element.setLayoutBoundsSize( NaN, NaN );
element.setLayoutBoundsPosition( xpos, ypos );

Pour ceux qui souhaitent obtenir un peu plus d'informations sur la manière de positionner les éléments radialement, référez vous à cet article.

Conclusion

Voilà, c'est la fin de ce premier tutoriel. Vous avez maintenant les bases pour créer vos propres modèles de mise en page.

Code source

Voici les sources des 2 applications de cette page:
flex4-layouts-sources.zip

En savoir plus