Flex Item Renderer – Modifier la taille d'un item avec des Transition
Dans l'article précédent, j'ai montré comment utiliser des States dans un itemRenderer pour modifier son apparence. Comme pour tous les composants qui héritent UIComponent, vous pouvez utiliser des Transition entre vos State. On va ici s'intéresser à la manière par laquelle on pourrait avoir des item qui s'étendent (dont la hauteur est modifiée).
La question de l'expansion d'un item devient intéressante quand on sait que la liste doit être scrollée. Imaginez cette situation: vous avez une liste d'items ayant la même hauteur. Maintenant, vous étendez l'item 2. Jusque là tout va bien, l'item 2 est plus grand que les autres items visibles. Et c'est là le problème: les items visibles. Maintenant vous faîtes un scroll dans la liste. Souvenez-vous que les itemRenderers sont recyclés. Donc quand l'item 2 est hors de vue, son itemRenderer sera bougé en bas de la liste. Vous devez donc mettre à zéro sa taille. OK, jusque là cela fonctionne. Maintenant faire un scroll vers le haut pour ré-afficher l'item 2. Vous voudriez qu'il soit dans l'état étendu. Comme l'itemRenderer pourrait-il le savoir? Si vous avez lu les articles précédents, vous savez que cette information vient soit de la data, soit d'une source externe.
Créer un itemRenderer resizable pour cela est assez complexe et pas forcement utile. Il y a une meilleure manière de faire cela en utilisant une VBox et un Repeater. Seulement, le problème avec le Repeater est que chaque child sera crée. Si vous avez 1000 enregistrement et que vous utilisez un Repeater, vous aurez 1000 instances de votre itemRenderer.
Pour cet exemple, on va écrire un itemRenderer, que l'on va utiliser en tant qu'enfant d'une VBox. Les éléments de cette liste sont simples: le nom et l'auteur du livre. Mais cliquer sur l'itemRenderer va le faire s'étendre. Ceci se fait en 2 étapes:
- L'itemRenderer a une état qui inclut les informations supplémentaires
- L'itemRenderer utiliser une transition Resize pour donner une fluidité dans la contraction/expand de l'itemRenderer
L'état de base de l'itemRenderer est plutôt simple:
<mx:HBox width="100%">
<mx:Label text="{data.author}" fontWeight="bold"/>
<mx:Text text="{data.title}" width="100%" fontSize="12" selectable="false"/>
</mx:HBox>
L'état étendu ("ExpandedState") ajouter des éléments qui vont agrandir l'itemRenderer.
Attention, pour une utilisation dans un composant mx:List, n'oubliez pas de fixer la propriété variableRowHeight à true
<mx:states>
<mx:State name="ExpandedState">
<mx:AddChild position="lastChild">
<mx:HBox width="100%">
<mx:Image source="{data.image}"/>
<mx:Spacer width="100%"/>
<mx:Label text="{data.price}"/>
<mx:Button label="Buy"/>
</mx:HBox>
</mx:AddChild>
</mx:State>
</mx:states>
Pour changer la taille de manière fluide, on ajoute simplement une transition:
<mx:transitions>
<mx:Transition fromState="*" toState="*">
<mx:Resize target="{this}" />
</mx:Transition>
</mx:transitions>
Les tags mx:states et mx:transitions doivent être placés à la racine de votre itemRenderer.
Les transitions sont appliquées quelque soit le basculement de state, car les propriétés fromState et toState sont des jokers (*). Maintenant, il ne vous reste plus qu'a prendre en charge le clic sur l'itemRenderer (en ajouter un évènement click sur le tag root) pour changer l'état:
private function expandItem() : void{
if( currentState == "ExpandedState" ){
currentState = "";
}else{
currentState = "ExpandedState";
}
}
Le résultat (catastrophique)
Flex Source Code Download: Télécharger le code source complet de l'application
Testez par vous mêmes, ouvrez plusieurs items et faites un scroll. Les items vont se mélanger, certains items vont se fermer/ouvrir magiquement. Bref, pas du tout le comportement escompté.
Alors comment faire ?
Vous devez vous en douter, d'autres se sont posé la question et sont arrivés à leur fin. Parmi eux, en voici un qui fonctionne bien, crée par FlashGuru. Pour cela, ils stockent l'état dans un objet au niveau du composant list et si fient ensuite à l'internal_uid (identifiant unique) pour savoir si l'item est étendu ou pas au niveau du set data(). C'est un peu feinté mais terriblement bien joué. Voici l'itemRenderer en question:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas
xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%"
height="{ (this.owner as EList).rowHeight }"
doubleClickEnabled="true"
doubleClick="this.onDoubleClick()"
horizontalScrollPolicy="off"
verticalScrollPolicy="off"
mouseChildren="false">
<mx:Resize id="expand" target="{this}" heightTo="80" duration="150"/>
<mx:Resize id="contract" target="{this}" duration="150"/>
<mx:Script>
<![CDATA[
import mx.utils.ObjectUtil;
import mx.controls.Alert;
private var rendererList:Object;
private function onDoubleClick( e:MouseEvent = null):void{
if (rendererList.expanded){
contract.heightTo = (this.owner as EList).rowHeight;
contract.end();
contract.play();
}else{
expand.end();
expand.play();
}
rendererList.expanded = !rendererList.expanded;
}
[Bindable]
override public function get data():Object{
return super.data;
}
override public function set data( value:Object ):void{
if (value){
super.data = value;
var list:EList = this.owner as EList;
if(list.rendererData[super.data.mx_internal_uid] == null){
rendererList = new Object();
rendererList.expand = false;
list.rendererData[super.data.mx_internal_uid] = rendererList;
}else{
rendererList = list.rendererData[super.data.mx_internal_uid];
}
this.height = rendererList.expanded?expand.heightTo:(this.owner as EList).rowHeight;
}
}
]]>
</mx:Script>
<mx:VBox
height="100%"
width="100%"
horizontalAlign="left"
verticalAlign="top">
<mx:HBox>
<mx:Image source="{data.thumb}" width="27" height="27"/>
<mx:Canvas verticalScrollPolicy="off">
<mx:Text text="{data.username}" selectable="false" fontSize="13" fontWeight="bold" textAlign="left" x="0" y="0"/>
<mx:Text text="{data.location}" selectable="false" fontSize="11" textAlign="left" x="0" y="18"/>
</mx:Canvas>
<mx:Spacer width="100%"/>
</mx:HBox>
<mx:Text text="{data.profile}" width="100%" selectable="false"/>
</mx:VBox>
</mx:Canvas>
Et voila l'application exemple:
Flex Source Code Download: Télécharger le code source complet de l'application
Articles similaires
- Flex Item Renderer – Communication entre un itemRenderer et l'application
- Flex Item Renderer – Créer un ItemRenderer externe (MXML et ActionScript)
- Flex Item Renderer – Communiquer avec un itemRenderer grâce à des Event
- Flex List – Bug de padding lors de l'utilisation d'une mx:List avec un itemRenderer
- Flex Item Renderer – Créer un ItemRenderer réutilisable avec listData
Aucun trackbacks pour l'instant






1 mai 2010
Bonjour et merci pour ce tutoriel !
J'avoue ne pas avoir tout compris (je suis encore un novice parmis tant d'autre ^^), cependant j'ai réussi a implémenter cette technique sans soucis, seul petit truc j'aurais voulu faire un contract de tous les éléments de ma list lorsque je clique sur un élément déjà contracté je ne suis peut-être pas très clair, voici un petit exemple :
lorsque j'ouvre mon appli chaque article est contracté de base, lorsque je clique sur un article il s'étend, si je clique sur un autre article celui d'avant reste étendu et celui sur lequel j'ai cliqué s'étend a son tour par conséquent je perd en gain de place sur ma page, j'aurais donc voulu savoir si j'avais la possibilité de contracté tous mes articles lorsque je clique sur un article en particulier pour pouvoir ensuite étendre celui-ci.
Encore merci pour ton tuto, et merci d'avance pour vos réponses !
2 mai 2010
Salut,
A mon avis, il faut que tu essaie de réinitialiser la variable "expanded" de l'objet "rendererList" qui est crée dynamiquement oar les renderers.
genre si ton dataProvider s'appelle "dp", un petit:
Tu peux boucler sur les items de ton dataProvider et récupérer l'itemRenderer (avec la méthode indexToItemRenderer:
http://livedocs.adobe.com/flex/201/langref/mx/controls/listClasses/ListBase.html#indexToItemRenderer%28%29
for each (var o:Object in dp){
// récupérer le renderer
if (renderer.rendererList =! null){
renderer.rendererList.expanded = false;
}
}
ou alors essayer d'accéder à l'effet contract et de faire un end() puis un play() dessus.
Voilà, bonne chance
Fabien
7 mai 2010
Coucou Fabien,
Merci de t'être donné la peine de répondre !
Désolé de ne pas avoir pu te répondre plutôt
Merci pour le conseil !!!
9 mai 2010
Hello c'est encore moi, bon j'avoue que je coince toujours autant...
Voici un peu de code :
Donc cette fonction est situé dans mon composant ItemRenderer, j'obtiens bien le nombre d'éléments que je devrais avoir seul problème la méthode indexToItemRenderer() n'a pas l'air d'être utilisable (l'autocompletion ne la trouve pas) ou alors je m'y prend mal.... ce qui est fort possible ^^'
Il faudrait que je fasse un truc dans ce genre : rendererList = indexToItemRenderer(i) ?
ou peut-etre tout simplement un : indexToItemRenderer(i) ?
Enfin quoi qu'il en soit je n'ai pas accés a cette methode flex me dit qu'elle ne semble pas definis (je suis sous flex 3).
Merci d'avance !!!
9 mai 2010
La méthode indexToItemRenderer se trouve sur la classe ListBase donc toutes les classes qui héritent de ListBase ont cette méthode, comme List par exemple
Fabien
11 mai 2010
Merci pour ta réponse finalement j'ai fait autrement je ne m'en sortais pas!
15 juin 2010
Bonjour,
tout d'abord merci pour tous ces tutoriels, je débute en Flex et je trouve souvent les réponses à mes questions dans vos pages.
J'ai le même problème que décrit ci-dessus mais sur un DataGrid dont une des colonne est gérée par un ItemRenderer. A chaque fois que je fais un scroll, les images de ma colonne sont modifiées/disparaissent.... Bref, cela fait n'importe quoi !
J'ai des soucis pour transposer votre exemple sur le mien.
J'ai récupéré la fonction "override set data"
mais je ne sais pas quoi faire de cette ligne "var list:EList = this.owner as EList;" dois je la laisser telle quelle ?
De même pour cette ligne "list.rendererData[super.data.mx_internal_uid] = rendererList;
"
J'ai fais plusieurs essai mais aucun m'a permis de régler mon problème.
Merci d'avance pour votre aide !
Cordialement,
15 juin 2010
Salut Thibault,
j'aimerai bien jeter un coup d'oeil à ton itemRenderer si possible. Pour les images, tu peux utiliser un composant qui gère le cache, comme cela, Flex ne va pas retélécharger les images à chaque apparition d'un item:
http://thanksmister.com/index.php/archive/flex-imagecache-a-cheap-way-to-cache-images/
http://www.quietlyscheming.com/blog/2007/01/23/some-thoughts-on-doubt-on-flex-as-the-best-option-orhow-i-made-my-flex-images-stop-dancing/
Cela devrait te permettre un meilleur fonctionnement. Si tu postes du code dans le commentaire, n'oublie pas les tags (voir en bas de page) et si ca ne marche pas, envoie le moi par mail (voir en bas de page aussi)
Fabien
15 juin 2010
Bonjour et merci pour la réponse.
Voilà mon itemRenderer tel qu'il ai sans tenir compte de ce tuto :
Le principe est que je récupère un état d'un catalogue et en fonction de cet état j'affiche des images pour chaque état qu'il a traversé....
Mon gros problème est que si je scroll sur mon DataGrid, j'ai toutes mes images qui sont affichés bizarement.
Si le code est trop compliqué, je veux bien essayer de le présenter plus simplement.
Je suis aussi preneur de toutes les remarques sur la façon de l'écrire.
Merci d'avance,