Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox


Barre de menus dynamiques

Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par Nataly, le 19 août 2011

Qu’il s’agisse de naviguer d’une page à une autre, de choisir des images à afficher ou des actions à effectuer, le menu - ou plus précisément la barre de menus - est un classique parmi les premières problématiques qui se présentent à un développeur novice.

Aussi, je vous propose aujourd’hui une technique, parmi tant d’autres, pour réaliser un menu comme illustré un peu plus bas1) :

On va procéder par étapes : d’abord la construction avec une structure “en dur” (décrite dans le .fla lui même), puis le même menu mais via un fichier .xml.

La structure du menu sera décrite via un tableau deux dimensions, je considère donc que vous êtes au point du principe. Si ce n’est pas le cas un petit détour par ce tuto vous sera certainement utile.

Pour l’étape suivante, il vous faudra bien sûr savoir charger un fichier et parcourir une structure xml (E4X). (ici une fiche mémo)

Quant aux visuels constituant les menus et leurs boutons de commande ce seront des symboles que nous ajouterons dynamiquement


Vocabulaire


Il va être beaucoup question dans ce tuto de menus, de barre de menus et de boutons de commande. Pure question de terminologie dont il convient de se mettre d’accord tout de suite pour palier à tout malentendu.

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

Ce que nous allons construire, pour moi c’est une barre de menus, elle même composées de plusieurs menus (Accueil, Galeries, Infos, L'équipe), chaque menu comptant un ou plusieurs bouton de commande et un seul bouton menu.
Par exemple le menu Galeries comporte un bouton de menu (Galeries) qui, au survol, déploie 3 boutons de commande (Nature, fleurs, divers).

Pour alléger le discours, j'emploierai le plus souvent bouton(tout court) pour bouton de menu et commande pour bouton de commande.

Voilà pour le vocabulaire, maintenant qu’on met la même chose sous les mêmes mots, on peut se lancer.

Préparer la bibliothèque

Puisque nous allons devoir afficher d’une part des boutons de menus et d’autre part des boutons de commande, il nous faut ces deux éléments dans la bibliothèque.
J’utilise le mot boutons, parce que c’est comme ça qu’on pense en français, mais ne nous précipitons pas pour autant à fabriquer des symboles boutons, dans ce cas de figure un symbole de type clip sera plus approprié puisque, vous le savez, avec un clip on peut avoir le beurre et l’argent du beurre.

Je vous laisse fabriquer deux symboles à votre goût, convenons seulement qu’ils contiendront un champ texte dynamique pour l’intitulé (chez moi txtTitre).
Je les nomme Mv_BtMenu pour les boutons de menu et Mv_BtCmd pour les boutons de commande. Faites bien comme vous voulez, mais pensez à cocher la case ‘exporter pour action script’ dans les propriétés afin de pouvoir les ajouter dynamiquement ;)

Si vous préférez vous contenter de mes moches boutons, voici la bibli au format CS4


Maintenant qu’on a tout le matériel, on rapatrie un café et on passe au code.

Etape 1

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

Stocker les éléments dans un tableau 2D


Première étape, générer autant d'instances des symboles de bibliothèque que nécessaire (Mv_BtMenu et Mv_BtCmd), écrire un titre (libellé) et se les garder sous le coude.
Le plus adapté c’est le tableau, et carrément le tableau deux dimensions.

Considérons un tableau pour chaque menu (composé d'un bouton de menu ainsi que des boutons de commande qui en dépendent).
Chacun de ces tableaux sera stocké dans un autre tableau (_tbBarreMenu) qui représentera donc la barre de menus.
A chaque emplacement de _tbBarreMenu on aura un tableau dont le premier élément (index 0) est un bouton de menu (instance de Mv_BtMenu) et les autres éléments des boutons de commande (instances de Mv_BtCmd).
Et on profitera de la caractéristique dynamique de la classe MovieClip pour faire porter, le cas échéant, aux instances considérées des infos (données) qui pourraient nous être utiles.

Décidons d’une fonction initTableau qui… je le dis ? … initialise le tableau ;)

Pour jouer et être certains qu’on construit bien quelque chose de souple, je propose que le nombre des commandes par menu soit aléatoire disons de 1 à 4, et décidons que la fonction initTableau attend un paramètre : le nombre de menus.

Et on s’y met :

Le tableau “barre de menus”, on va s'y référer de partout dans le code, ce sera donc une variable globale qu’on déclare sans plus attendre :

var _tbBarreMenu:Array=new Array();



Conventions d'écriture

Il est conventionnellement admis que les variables visibles de partout dans un projet (plus tard une classe), dites variables globales, sont préfixées du caractère de soulignement (underscore) '_'.

Autant prendre de bonnes habitudes, c'est une convention qui en vaut une autre, la respecter aide furieusement à s'y retrouver dans le code. Plus tard quand vous reprendrez vos sources2), lire et comprendre ce que fait telle fonction que vous saurez devoir modifier sera bien plus simple :
• une variable préfixée d'un p, c'est un paramètre de la fonction, on le reçoit tout fait (déjà valorisé) et on en connait le type ds la signature.
• une variable pas préfixée est déclarée et “calculée” (valorisée) dans le corps de la fonction.
• une variable préfixée du caractère de soulignement, c'est une globale… Et là, il convient d'y regarder d'un peu plus près, mais elle est déclarée et typée en tête de code…


Rien de particulier à commenter pour la fonction elle même, je vous laisse faire.

var _tbBarreMenu:Array=new Array();
 
initTableau(5);
 
function initTableau(pNb:int) {
	var leBouton:MovieClip; // De type MovieClip pcq parent de Mv_BtMenu() et de Mv_BtCmd() 
	                        // Et que partout où on attend la mère on peut envoyer la fille ;-)
	for (var i:int=0; i<pNb; i++) {
		var tbTemp:Array=new Array(); // tableau temporaire représentant un menu
		leBouton=new Mv_BtMenu(); // leBouton attend du MovieClip, on peut donc envoyer la fille : Mv_BtMenu
		leBouton.txtTitre.text="Menu "+i; // un intitulé
		tbTemp.push(leBouton); // ajouter au tableau "menu"
		var aa:int=Math.random()*4+1; // un nombre aléatoire de commandes
		for (var k:int=0; k<aa; k++) {
			leBouton=new Mv_BtCmd();// leBouton attend du MovieClip, on peut donc envoyer la fille : Mv_BtCmd
			leBouton.txtTitre.text="cmd "+i+k; // un intitulé
			tbTemp.push(leBouton);// ajouter au tableau "menu"
		}
		_tbBarreMenu.push(tbTemp);// ajouter le tableau "menu" au tableau "barre de menus"
	}
	trace("menu "+_tbBarreMenu);
}

calque : etape1#1 ds les sources

Hop ! On dispose maintenant de tous les boutons, aussi bien de commande que de menu, avec leur intitulé, yapuka…

La fonction fabMenus

… fabriquer la barre de menus proprement dite, je veux dire afficher les boutons menus et faire en sorte qu'au survol, surgissent les boutons de commande associés.

C'est la fonction fabMenus qui va s'en charger et nous renvoyer un clip tout fini, tout prêt à être ajouté à la liste d'affichage où bon nous semblera (sur la scène, dans un clip…).

Comme toujours on commence par la fin, c'est à dire par imaginer comment on l'utilisera.
Puisqu'elle renverra un clip, je choisis cette forme :

addChild(fabBarreMenu(50,10));


Ça m'arrange de l'afficher en une seule ligne (donc de lui passer ses futures coordonnées x et y) mais rien ne vous y oblige, vous pouvez opter pour l'option :
 
var laBarreDeMenu:MovieClip=fabBarreMenu();
laBarreDeMenu.x=50;
laBarreDeMenu.y=10;


En français

Que va-t-elle faire cette fonction ?

Fabriquer un clip “barre de menus” destiné à contenir autant de boutons de menu que nécessaire

Ajouter (à ce clip) chacun des boutons de menu, et les disposer3).

• Faire en sorte qu'au survol des boutons de menu quelque chose se passe (à savoir déploiement des commandes) et que quelque chose d'autre se passe quand on quitte le survol (en l'espèce faire disparaitre les boutons de commande). Je dis qu'il se passe quelque chose pour signifier qu'on va écouter ces événements et faire le quelque chose en question depuis les fonctions de rappel.
A ce stade, on se contentera d'un trace ;-)

Renvoyer le clip “barre de menus”, tout fini tout fonctionnel (trop fort).

Le mieux c'est encore de compléter/modifier initTableau4).
Au point où en est de fabriquer les boutons et de les stocker, autant en profiter pour afficher ceux qui doivent l'être (les boutons de menu).


Indices

• Dans la catégorie anticipation, ne nous contentons pas d'ajouter les boutons de menu directement dans le contenant barreMenu. En effet, dès qu'il va s'agir de coder le survol, on va s'apercevoir que c'est tout un m…r de disposer les boutons de commande.
En plus il s'agit que le ROLL_OUT ne soit pas appelé quand on passe d'une commande à l'autre.
C'est donc infiniment plus malin de considérer qu'un menu est un clip contenant un bouton (au minimum) et des commandes (quand il est déployé).



En bonus je décide que les boutons seront espacés, par défaut, de cinq pixel. Comme je suis gentille avec moi même je me laisse l'opportunité de passer une valeur de mon choix, ce qui implique un paramètre supplémentaire et facultatif (ici pDeltaY) et je passe aussi le nombre de menus en facultatif, pour alléger l'écriture.

Voici donc la signature :

fabBarreMenu(px:Number,py:Number,pDeltaY:Number=5,pNb:int=5):MovieClip


A vous de jouer !

En AS3


var _tbBarreMenu:Array=new Array();
 
addChild(fabBarreMenu(50,10));
//
//
function fabBarreMenu(px:Number,py:Number,pDeltaY:Number=5,pNb:int=5):MovieClip {
	var barre:MovieClip=new MovieClip();// un CLIP pour la barre de menu toute entière
	var leBouton:MovieClip;
	for (var i:int=0; i<pNb; i++) {
		var tbTemp:Array=new Array();
		var menu:MovieClip=new MovieClip();// un CLIP pour l'affichage du menu
		leBouton=new Mv_BtMenu();
		leBouton.txtTitre.text="Menu "+i;
		leBouton.addEventListener(MouseEvent.CLICK,qdClicBtMenu);// si on voulait intercepter le clic sur un bouton de menu…
		tbTemp.push(leBouton);
		menu.addChild(leBouton);// afficher le bouton dans le contenant "menu"
		menu.y = i * (leBouton.height + pDeltaY);
		menu.idx=i;// pour identifier (plus tard) le tableau correspondant dans _tbBarreMenu
		menu.addEventListener(MouseEvent.ROLL_OVER,qdSurvolMenu);
		menu.addEventListener(MouseEvent.ROLL_OUT,qdQuitteMenu);
		barre.addChild(menu);
 
		// On n'affiche pas les commandes, ce sera fait au survol
		var aa:int=Math.random()*4+1;
		for (var k:int=0; k<aa; k++) {
			leBouton=new Mv_BtCmd();
			leBouton.txtTitre.text="cmd "+i+k;
			leBouton.addEventListener(MouseEvent.CLICK,qdClicCmd); // mais on ajoute tout même l'écouteur, ce sera fait
			tbTemp.push(leBouton);
		}
		_tbBarreMenu.push(tbTemp);
	}
	//disposer
	barre.x=px;
	barre.y=py;
	//on renvoie une barre de menu fonctionnelle
	return barre;
}

calque : etape1#2 ds les sources

Les fonctions de rappel

Pour tester, vous êtes contentés d'une ligne dans les fonctions de rappel5) du genre trace(“je passe dans fonction truc”), il va maintenant s'agir de coder de façon plus convaincante.

Au survol

C'est tout bête, je n'ai pas grand chose à vous dire : on récupère le menu concerné via la propriété currentTarget (et non target, c'est plus sûr) et on lui ajoute les commandes stockées ds _tbBarreMenu.

J'ai choisi de passer par une fonction afficheCmd que j'invoque depuis la fonction de rappel. Je vous accorde que sur ce coup là, on aurait tout aussi bien pu en écrire le corps directement dans la fonction de rappel.
Mais
D'une part ça me permet de remettre une couche sur le transtypage 6), d'autre part, on sait jamais, ça peut être pratique de pouvoir appeler afficheCmd de n'importe où pour déployer un menu. Ça ne mange pas de pain de “la sortir”… ;)

Indices

• J'attire votre attention sur le fait qu'il va bien falloir, dans les fonctions de rappel, savoir de quel menu il s'agit (pour ajouter les commandes concernées). La bonne idée c'est d'utiliser la caractéristique dynamique de la classe MovieClip (le fait d'ajouter des variables “à la volée”) pour stocker cette info au moment où on l'a sous la main.
Qu'est-ce qui identifie un menu dans _tbBarreMenu ?
Son numéro d'index…
… je ne vous en dis pas plus sans quoi ce n'est plus drôle. A vous d'ajouter la ligne qui va bien, là où elle va bien ;)



//================================================
function qdSurvolMenu(me:MouseEvent):void {
 
	// afficheCmd attend expressément du MovieClip, on lui envoie donc ce qu'elle attend
	afficheCmd(MovieClip(me.currentTarget));
        // c'est pcq on reçoit un l'objet (me.currentTarget), qu'il faut transtyper  
        // (ce qui revient à affirmer "ceci est un clip" - ça tombe bien, on en est sûrs -)
}
// -----------------------------------
// affiche les boutons de commande
function afficheCmd(pCont:MovieClip):void {
	// le menu concerné est décrit à l'index stocké dans la propriété idx
	var max:int = _tbBarreMenu[pCont.idx].length;
	var largBtMenu:Number = _tbBarreMenu[pCont.idx][0].width;
	for (var i:int= 1; i<max; i++) {
		var cmd:Mv_BtCmd = Mv_BtCmd(_tbBarreMenu[pCont.idx][i]);
		pCont.addChildAt(cmd,0);// on ajoute dessous, parti-pris esthétique
		cmd.x = (i - 1) * cmd.width + largBtMenu;
	}
}


La ligne qui va bien.
Pour identifier le menu qui a fait l'objet du survol, pas la peine de se compliquer. Il suffit à la création (dans fabMenu, donc) de prendre soin de “noter” le numéro d'index du tableau concerné (via une variable dynamique idx ajoutée pour l'occasion).

menu.idx=i;// pour identifier (plus tard) le tableau correspondant dans _tbBarreMenu



Quitter le survol


C'est bien parce que je suis une grande bavarde, mais à part attirer votre attention sur le fait qu'il ne faut virer que les commandes (pas le bouton de menu lui même :mrgreen:), je ne vois pas quoi commenter.

//================================================
function qdQuitteMenu(me:MouseEvent):void {
 
	var max:int=_tbBarreMenu[me.currentTarget.idx].length;
        // on ne supprime que les boutons de commande --> depuis index 1 
	for (var i:int= 1; i<max; i++) {
		// Aller chercher - et virer - depuis _tbBarreMenu les boutons de commande du menu considéré (donc à partir de l'index 1)
		var cmd:Mv_BtCmd=Mv_BtCmd(_tbBarreMenu[me.currentTarget.idx][i]);
		me.currentTarget.removeChild(cmd);
	}
}


Une autre façon de s'y prendre, si on a ajouté les commandes sous le bouton de menu :

function qdQuitteMenu(me:MouseEvent):void {
	var max:int=_tbBarreMenu[me.currentTarget.idx].length;
	while (me.currentTarget.numChildren>1) {
		me.currentTarget.removeChildAt(0);
	}
}

Le clic sur les boutons de commande


Pour vérifier que tout fonctionne comme on l'entend, je vous propose d'ajouter un champ texte (dynamique) et de faire en sorte qu'au clic sur une commande s'y inscrive un truc du genre “j'ai cliqué sur le bouton…” …suivi de son intitulé.

function qdClicCmd(me:MouseEvent):void {
	txtSortie.text="clic sur cmd "+me.currentTarget.txtTitre.text;
}



Le clic sur les boutons de menu


Vraiment histoire de dire qu'on fait tout comme il faut, une ligne pour écrire quelque chose dans le champ texte si on clique sur un bouton menu.

function qdClicBtMenu(me:MouseEvent):void {
	trace(me.currentTarget.txtTitre.text);
	txtSortie.text = "clic sur menu : " + me.currentTarget.txtTitre.text;
}


calque : fct rappel ds les sources


Une description "en dur"

Bon.
Une barre de menus aléatoire, c'est bien pour s'échauffer et éviter le claquage, mais c'est carrément inutile. Il nous faut donc décrire vraiment la barre de menus.

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

Il faut “dire” : Je veux une barre composée d'un premier menu qui s'appellera Accueil sans commandes, un autre qui s'appellera Galerie avec trois commandes Nature, Fleurs, Divers, un autre qui s'appellera Infos… et ainsi de suite.
Il faut dire aussi, ce qu'il va se passer quand on clique sur les commande. Du moins préparer le terrain pour que la fonction qdClicCmd puisse faire autre chose qu'afficher le nom de la commande dans un champ texte.
On pourrait, au clic, envoyer la tête de lecture sur une image précise, ajouter un symbole de bibliothèque, charger un swf…

J'opte pour un fichier à charger.

Le mieux c'est encore d'organiser ces infos (données) dans un tableau 2D selon la même structure que _tbBarreMenu. On décide qu'on va y stocker des objets équipés de deux propriétés : lbl pour l'intitulé et url pour l'adresse du fichier à charger.

var _tbBarreMenu:Array;
var _tbData:Array=[
[{lbl:"Accueil"}],
[{lbl:"Galeries"},{lbl:"Nature",url:"nature.swf"},{lbl:"fleurs",url:"fleurs.swf"},{lbl:"Divers",url:"divers.swf"}],
 [{lbl:"Infos"},{lbl:"du jour",url:"truc.swf"},{lbl:"du mois",url:"truc.swf"}],
 [{lbl:"L'équipe"},{lbl:"machin",url:"truc.swf"},{lbl:"chose",url:"truc.swf"}]];
 
 
addChild(fabMenu(_tbData,10,10));


Modifier fabMenu


La fonction fabMenu, telle qu'elle est écrite, attend un entier pour fabriquer autant de menus que précisé (5 par défaut).
Maintenant c'est un tableau qu'elle reçoit. Elle ne va donc plus boucler x fois et écrire un numéro d'incrément dans les boutons mais boucler sur le nombre de menus et écrire la valeur de la propriété lbl de l'objet stocké (dans _tbData).

Voilà, il ne vous reste plus qu'à apporter les minuscules modifications nécessaires :)

// fabrique et renvoie une barre menu SANS boutons de commande : ils sont ajoutés au survol
function fabMenu(pTab,pX:int,pY:int,pDeltaY:Number=5):MovieClip {
	_tbBarreMenu=new Array();// tableau 2D de clips
	var barreMenu:MovieClip=new MovieClip();
 
	var tbTemp:Array;// un tableau de clips par menu (un bouton + des commandes)
	var leBouton:MovieClip;
	var nbMenus:int=pTab.length;// nombre de menus                                 - ICI -
	for (var i:int =0; i<nbMenus; i++) {//                                                        
		// contient le bouton de menu et contiendra aussi les boutons de cmd;
		var menu:MovieClip=new MovieClip();
 
		tbTemp = new Array();// un nouveau tableau menu
		leBouton=new Mv_BtMenu();// le  bouton
		tbTemp.push(leBouton);
 
		leBouton.addEventListener(MouseEvent.CLICK,qdClicBtMenu);
		leBouton.txtTitre.text=pTab[i][0].lbl;// son intitulé                   - ICI -
		menu.addChild(leBouton);//ajoute bouton au clip menu
		menu.y=i*(leBouton.height+pDeltaY);
		menu.idx=i;// pour identifier (plus tard)le tableau à utiliser dans _tbBarreMenu
		menu.addEventListener(MouseEvent.ROLL_OVER,qdSurvolMenu);
		menu.addEventListener(MouseEvent.ROLL_OUT,qdQuitteMenu);
		barreMenu.addChild(menu);//// ajoute le menu à la barre
 
		// les boutons de commandes pour chaque menu
		var max:int=_tbData[i].length;// nombre  de commandes                  - ICI -  
		for (var k:int=1; k<max; k++) {//                          
			leBouton=new Mv_BtCmd();// nouveau bouton de commande
			leBouton.txtTitre.text=pTab[i][k].lbl;// son intitulé          - ICI -
			leBouton.ref=pTab[i][k].url;// l'adresse du fichier à charger  - ICI - 
			leBouton.addEventListener(MouseEvent.CLICK,qdClicCmd);
			tbTemp.push(leBouton);
		}
		_tbBarreMenu[i]=tbTemp;// ajouter le menu à la barre de menus
	}
	barreMenu.x=pX;
	barreMenu.y=pY;
	return barreMenu;
}

calque : etape1#3 ds les sources

Eh ben voilà, plus qu'à modifier qdClicCmd et écrire qdClicBtMenu.

function qdClicCmd(me:MouseEvent):void {
	trace(me.currentTarget.txtTitre.text);
	txtSortie.text="clic sur "+me.currentTarget.txtTitre.text+"- charge "+me.currentTarget.ref;
}
 
 
function qdClicBtMenu(me:MouseEvent):void {
	trace(me.currentTarget.txtTitre.text);
	txtSortie.text="";
	if (me.currentTarget.txtTitre.text=="Accueil") {
		txtSortie.text="clic sur Accueil";
	}
}


Etape 1,5

Woa, non ! Je ne peux vraiment pas titrer étape 2, ce n'est pas une étape, c'est une une récrée, une pause, un trou normand, pour se faire plaisir et digérer avant de d'attaquer le plat de résistance (le fichier .xml) ;)

On va ajouter un peu de mouvement.
Plutôt qu'afficher les visuels commandes brutalement à leur place définitive, on va leur appliquer un effet : une transition, un déplacement, les deux… Libre à votre créativité.

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

Quand on pense mouvement vite-fait-bien-fait, on pense tween, et c'est bien ce que je vous propose d'utiliser, si ce n'est qu'il va falloir se méfier du ramasse miettes (garbage collector).

Les tweens et le ramasse-miettes

Petite démo vite fait pour éviter un long discours :

import fl.transitions.Tween;
import fl.transitions.easing.*;
 
for (var i:int= 1; i<10; i++) {
	var truc:Mv_Truc = new Mv_Truc();
	addChild(truc);
	truc.y=i*50;
	var tw:Tween=new Tween(truc,"x",Back.easeOut,0,400,3,true);
}


Voilà qui fabrique une poignée d'instances d'un symbole quelconque, les aligne les unes sous les autres et leur imprime un déplacement horizontal.

Jusque là… ça va.

Obligeons le ramasse miette à faire son job7):

for (var i:int= 1; i<10; i++) {
	var truc:Mv_Truc = new Mv_Truc();
	addChild(truc);
	truc.y=i*50;
	var tw:Tween=new Tween(truc,"x",Back.easeOut,0,400,3,true);
	System.gc(); //obligeons le ramasse miette à passer
}


Que constate-t-on ?
Seul le dernier symbole est animé. Normal : le ramasse miettes supprime tous les objets non référencés (associés à une variables ou stockés ds un tableau).
Ici on utilise la même variable dont on “écrase le contenu” à chaque tour de boucle, les objets tween sont donc lâchés dans la nature et éligibles au ramasse miette. Seule la dernière instance référencée par la variable tw échappe au nettoyage et seul le dernier clip est animé.

Puisqu'on ne sait jamais quand le ramasse miette va se mettre en fonction, il est éminemment conseillé d'être très vigilant et de ne pas laisser des objets tween non référencés ; on ne sait jamais…
Ici un moyen simple - plutôt que les stocker dans un tableau - c'est de fabriquer une variable dynamique sur chacune des instances pour préserver l'objet Tween d'un passage inopiné du ramasse miettes :

for (var i:int= 1; i<10; i++) {
	var truc:Mv_Truc = new Mv_Truc();
	addChild(truc);
	truc.y=i*50;
	truc.tw= new Tween(truc,"x",Back.easeOut,0,400,3,true);
	System.gc()
}


Et voilà ! :)

Enrichir la fonction afficheCmd

Adapté à notre préoccupation ça peut donner ça :

// affiche les boutons de commande
function afficheCmd(pCont:MovieClip):void {
	var max:int=_tbBarreMenu[pCont.idx].length;
	var largBtMenu:Number=_tbBarreMenu[pCont.idx][0].width;
	for (var i:int= 1; i<max; i++) {
		var cmd:Mv_BtCmd=Mv_BtCmd(_tbBarreMenu[pCont.idx][i]);
		cmd.addEventListener(MouseEvent.CLICK,qdClicCmd);
		pCont.addChildAt(cmd,0); //  pour faire plus propre ajouter les commandes sous le bouton de menu
//		cmd.x = (i - 1) * cmd.width + largBtMenu; // Avant
		cmd.tw = new Tween(cmd,"x",Bounce.easeOut,0,(i - 1) * cmd.width + largBtMenu,1,true);
	}
}


En “accrochant” l'objet tween à un clip (via la variable tw) on se prive de la possibilité de libérer les ressources une fois l'interpolation terminée, la libraire TweenLitle pourrait alors être une bonne réponse. Si ça ne vous dit rien, chance pour vous, Thoutmosis lui consacre un dossier, et ce chapitre traite plus précisément de ce point.

Etape 2 : depuis un fichier .xml

Pour générer un menu depuis une structure décrite dans un fichier xml il n'y pas grandes différences. Il s'agit seulement de charger un fichier .xml puis de le parcourir pour fabriquer la barre de menus et au passage le tableau contenant les différents boutons.

Le fichier xml

Il y a de multiples façons de concevoir le fichier .xml, selon le parti-pris, le code de lecture (en AS3) sera un peu différent. Ici j'ai décidé d'enregistrer en plus du nom du fichier à charger, le répertoire (dossier) où le trouver. J'ai opté pour le répertoire en tant qu'attribut (rep) et le libellé du bouton aussi (titre).

<?xml version="1.0" encoding="UTF-8"?>
<leMenu>
  <btMenu titre="Accueil">
  </btMenu>
  <btMenu titre="Galeries">
	<cmd titre="Nature" rep="photos/nature/">nature.swf</cmd>
	<cmd titre="Fleurs" rep="photos/fleurs/">fleurs.swf</cmd>
	<cmd titre="Divers" rep="">divers.swf</cmd>
  </btMenu>
  <btMenu titre="Infos">
	<cmd titre="Du jour" rep="infos/">infoJour.swf</cmd>
	<cmd titre="Du mois" rep="infos/archive/">infoMois.swf</cmd>
  </btMenu>
  <btMenu titre="L'équipe">
	<cmd titre="Machin" rep="">bookMachin.swf</cmd>
	<cmd titre="Chose" rep="">bookChoz.swf</cmd>
  </btMenu>
</leMenu>


Notez que j'ai pris soin d'écrire le “slash” en fin du nom de répertoire, ça nous permet de charger un fichier dans le même répertoire que celui du html appelant, sans se compliquer le code (divers.swf, par exemple, est réputé se trouver au même niveau que l'appelant).

Charger le fichier

Rien de bien terrible, chargeons le fichier comme de normal :

chargeXML("menu.xml");
function chargeXML(pAdresse:String) {
	var chargeurXML = new URLLoader();
	chargeurXML.addEventListener(Event.COMPLETE, finChargementXML);
	chargeurXML.load(new URLRequest(pAdresse));
}
 
function finChargementXML(e:Event):void {
	if ((e.target as URLLoader) != null ) {
		// nouvel objet XML contenant les données
		var xml:XML=new XML(e.target.data);
		xml.ignoreWhitespace=true;
//		trace(xml);
		e.target.removeEventListener(Event.COMPLETE, finChargementXML);
		// on appelle fabMenu avec les données du xml;
		addChild(fabMenu(xml,10,10));
	}
}

Modifier fabMenu

Les modifications sont minimes : on ne compte plus le nombre d'éléments du tableau mais le nombre de nœuds du .xml (la belle affaire) et on profite de parcourir le fichier pour stocker sous forme de propriétés dynamiques les attributs des nœuds cmd (titre et rep).

function fabMenu(pXml,pX:int,pY:int,pDeltaY:Number=5):MovieClip {
	_tbBarreMenu=new Array();// tableau 2D de clips
	var barreMenu:MovieClip=new MovieClip();
 
	var tbTemp:Array;// tableau de clips menu (un boutons des commandes)
	var btTemp:MovieClip;
	var nbMenus:int=pXml.btMenu.length();// nombre de menus                      -ICI-
	for (var i:int =0; i<nbMenus; i++) {//                                                        
		// contient le bouton de menu et contiendra aussi les boutons de cmd;
		var menu:MovieClip=new MovieClip();
 
		tbTemp = new Array();// un nouveau tableau menu
		btTemp=new Mv_BtMenu();// le  bouton
 
		btTemp.addEventListener(MouseEvent.CLICK,qdClicBtMenu);
		tbTemp.push(btTemp);
		btTemp.txtTitre.text=pXml.btMenu[i].@titre;// son intitulé           -ICI-   
		menu.addChild(btTemp);//ajoute bouton au clip menu
		menu.y=i*(btTemp.height+pDeltaY);
		menu.idx=i;// pour identifier (plus tard)le tableau à utiliser dans _tbBarreMenu
		menu.addEventListener(MouseEvent.ROLL_OVER,qdSurvolMenu);
		menu.addEventListener(MouseEvent.ROLL_OUT,qdQuitteMenu);
		barreMenu.addChild(menu);//// ajoute le menu à la barre
 
		// les boutons de commandes pour chaque menu
		var max:int=pXml.btMenu[i].cmd.length();//                            -ICI-
		for (var k:int=0; k<max; k++) {                             
			btTemp=new Mv_BtCmd();// nouveau bouton de commande
			btTemp.txtTitre.text=pXml.btMenu[i].cmd[k].@titre;//intitulé  -ICI-
			btTemp.repertoire=pXml.btMenu[i].cmd[k].@rep;//               -ICI-
			btTemp.fichier=pXml.btMenu[i].cmd[k];// nom du swf            -ICI-
			tbTemp.push(btTemp);
		}
		_tbBarreMenu[i]=tbTemp;// ajouter le menu à la barre de menus
	}
	barreMenu.x=pX;
	barreMenu.y=pY;
	return barreMenu;
}

D'autres voies

On vient donc de modifier la fonction fabMenu pour lui passer non-plus un tableau, mais un fichier .xml en se disant qu'un tableau décrit en dur dans le fichier source (.fla) ce n'est pas très malin en tous cas pas souple du tout.

C'est une façon de penser, en aucun cas la seule, ni même la meilleure… ni encore la pire ;)
C'en est une, parmi d'autres, comme je le disais en intro.

Imaginons par exemple que vous vous soyez, depuis belle heure, fabriqué la classe kivabien qui charge un .xml et le transforme en tableau 2D d'objets, peut-être une fonction fabMenus serait-elle plus adaptée…

Pour aller plus loin


En vrai, si vous avez souvent recours à des barres de menu, ou que vous en souhaitez plusieurs sur une même page (pour gérer des galeries, des morceaux de musique) vous aurez intérêt à vous faire une classe qui renvoie un menu (clip) équipé des événements judicieux((diffusés au clic sur les boutons).
Si ça vous intéresse, je vous donne rendez-vous ici.
Il ne me reste plus qu'à vous souhaiter bon appétit (avec un tel menu ça ne saurait être autrement :mrgreen:)

1) au chapitre vocabulaire
2) si, si : rêvez pas, on y revient inéluctablement
3) je décide arbitrairement qu'ils seront les uns sous les autres, libre à vous de choisir de les disposer horizontalement
4) en la renommant au passage pour que le code reste parlant
5) qdSurvolMenu, qdQuitteMenu, qdClicCmd…
6) vous savez la fameuse 1118 : contrainte implicite gnagnagna…
7) attention ce n'est possible que via le lecteur de test, dans un contexte d'utilisation normal, il se met en route quand ça lui chante, on n'a pas la main dessus