Diaporama CanalJ (partie 4)

Dans: Travaux

6 août 2011

Vue Générale

La phase de réalisation
Attention on s’accroche si vous n’avez jamais programmé ou vu de code ça va piquer les yeux.
A mon sens cela reste intéressant de survoler la partie réalisation juste pour la culture générale et pour démystifier la programmation.

Le flux XML existant que je vais devoir réutiliser

<?xml version="1.0"?>
<IMAGES>
	<IMG urlimage_min="http://www.yopsolo.fr/ressources/photos/small1.jpg" urlimage_grand="http://www.yopsolo.fr/ressources/photos/big1.jpg" urlRedirection="http://www.canalj.fr/La-tele/Dessins-animes/Gormiti" titre="Ils débarquent sur Canal J !!" description="Un monde menace le notre ! Celui de créatures vivant dans une dimension parallèle... des monstres portant le nom de GORMITIS !"/>
	<IMG urlimage_min="http://www.yopsolo.fr/ressources/photos/small2.jpg" urlimage_grand="http://www.yopsolo.fr/ressources/photos/big2.jpg" urlRedirection="http://www.canalj.fr/La-tele/Dessins-animes/Dinosaur-King" titre="Max attaque !" description="Dinosaur King, c'est l'histoire de Max et de ses amis qui découvrent de mystérieuses cartes. Lorsqu'elles sont activées, les dinosaures prennent vie."/>
	<IMG urlimage_min="http://www.yopsolo.fr/ressources/photos/small3.jpg" urlimage_grand="http://www.yopsolo.fr/ressources/photos/big3.jpg" urlRedirection="http://www.canalj.fr/La-tele/Dessins-animes/Linus-et-Boom" titre="Linus & Boom, c'est de la dynamite !" description="Découvre les aventures de Linus et son ami Boom l'extra-terrestre !"/>
	<IMG urlimage_min="http://www.yopsolo.fr/ressources/photos/small4.jpg" urlimage_grand="http://www.yopsolo.fr/ressources/photos/big4.jpg" urlRedirection="http://www.canalj.fr/La-tele/Dessins-animes/Spiez" titre="Spiez, les espions experts !" description="3 frères, Tony, Marc et Lee, et leur sœur Megan, ont été secrètement sélectionnés par le WOOHP - le World Office des Opérations Hautement Prioritaires - pour devenir des espions internationaux."/>
	<IMG urlimage_min="http://www.yopsolo.fr/ressources/photos/small5.jpg" urlimage_grand="http://www.yopsolo.fr/ressources/photos/big5.jpg" urlRedirection="http://www.canalj.fr/La-tele/Emissions/Iap-Iap" titre="L'émission IapIap! avec Khriss" description="En 2009, la nouveauté devient phare avec des Iapiap battle, des affrontements 1 contre 1, Les 15 gagnants des battles passeront dans les Iapiap rumble, les matchs à 3, enfin une finale à 5 finalistes.."/>
</IMAGES>

Pour traiter ce flux de données, je réutilise cette classe (un bloc de code autonome) que j’ai codé pour un autre projet.

package
{
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;

public class Config extends EventDispatcher
{
private static var _instance:Config;
private var _datas:XML;
public var err:String		= "";

public function Config( lock:SingletonLock ) { }

public static function get instance():Config {
if (_instance == null){
_instance = new Config(new SingletonLock());
};
return (_instance);
}

public function loadDatas( url:String ):void {
var loader:URLLoader = new URLLoader();
loader.addEventListener(IOErrorEvent.IO_ERROR, this._onError);
loader.addEventListener(Event.COMPLETE, this._onLoadComplete);
loader.load( new URLRequest( url ) );
}

private function _onLoadComplete( e:Event ):void {
e.stopImmediatePropagation();
_datas = XML( (e.target as URLLoader).data );
dispatchEvent(new Event(Event.COMPLETE));
}

private function _onError(e:IOErrorEvent):void {
err = e.text;
dispatchEvent( new Event(Event.CLOSE) );
}

// --
public function get datas():XML{
if( !_datas ){
throw (new Error("Exécuter la methode loadDatas() et ecouter Event.COMPLETE avant d'essayer d'acceder aux datas.") );
}
return _datas;
}

}
}

class SingletonLock { }

Je ne vais pas expliquer ligne par ligne sachez juste qu’il s’agit de la partie du code qui va récupérer les données (adresse des images, textes etc.) et de les rendre accessible au reste de l’application.

A ce point, nous avons les adresses des images et des vignettes reste juste à les charger.
Là je dois coder une nouvelle classe que je pourrai utiliser (instancier) 2 fois dans le projet.
Une pour le slider(661*390) principal et l’autre pour slider en haut a droite (95*95).

package
{
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.display.LoaderInfo;
import flash.geom.Point;

public class LulzSlider extends Sprite
{
private var _images:Vector. = new Vector.;
public var largeur_image:int;
public var hauteur_image:int;

public function LulzSlider() {
rotation		= -3;
mouseChildren	= false;
addEventListener( Event.ADDED_TO_STAGE, _onAddedToStage );
}

private function _onAddedToStage(e:Event):void{
while (numChildren) removeChildAt(0);
}

public function addImage( url:String ):void {
_images.push( new URLRequest(url) );
}

public function loadAll():void
{
var nb:int = _images.length;
for(var i:int = 0 ; i < nb ; i++)
{
var l:Loader = new Loader();
l.x = i * largeur_image;	// petit trick, 'placement du conteneur'
l.load( _images[i] );
l.contentLoaderInfo.addEventListener( Event.COMPLETE, _onComplete, false, 0, true ) ;
}
}

private function _onComplete(e:Event):void
{
var li:LoaderInfo	= e.target as LoaderInfo;
var bmp:Bitmap 		= li.content as Bitmap;
var dat:BitmapData	= new BitmapData( largeur_image, hauteur_image,true, 0x0);
dat.copyPixels( bmp.bitmapData, bmp.bitmapData.rect, new Point );
var clone:Bitmap = new Bitmap(dat, "auto", true );
clone.x = li.loader.x; 			// recup du placement précalculé plus haut ^^
addChild( clone );
bmp = null;
}

}
}

Cette classe décrit le comportement de l’objet qui va contenir les images, les images sont disposées en file indienne, comme elles ont une taille fixe leur abscisse est égal à : rang dans la file * largeur du visuel (661px ou 95px si il s’agit du petit slider)

Et pour finir le petit Menu vertical

package
{
import flash.display.Sprite;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import com.greensock.TweenNano;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.net.URLRequest;

public class MenuSlider extends Sprite
{
public static const TWEEN_COMPLETE:String	= "on_tween_complete";

private const TAILLE_IMG:int				= 64;

public var vignette1_mc:Vignette;
public var vignette2_mc:Vignette;
public var vignette3_mc:Vignette;
public var vignette4_mc:Vignette;
public var curseur_mc:Sprite;
private var curseur_oriX:int				= 6;
private var curseur_oriY:int				= 5;

private var _currentIdx:int 				= 0;
private var _isTweening:Boolean				= false;
private var _timer:Timer;

public var animation_mc:MovieClip

public function MenuSlider() {
buttonMode 		= true;
}

public function init(tempo:int = 1000):void
{
_timer			= new Timer( (tempo < 1000) ? 1000 : 5000 ,1 );
_timer.addEventListener( TimerEvent.TIMER, _onTimerComplete );

var datas:XML = Config.instance.datas;
vignette1_mc.id = 0;
vignette1_mc.mouseChildren	= false;
vignette1_mc.load( new URLRequest( datas.IMG[0].@urlimage_min ) );
vignette2_mc.id = 1;
vignette2_mc.mouseChildren	= false;
vignette2_mc.load( new URLRequest( datas.IMG[1].@urlimage_min ) );
vignette3_mc.id = 2;
vignette3_mc.mouseChildren	= false;
vignette3_mc.load( new URLRequest( datas.IMG[2].@urlimage_min ) );
vignette4_mc.id = 3;
vignette4_mc.mouseChildren	= false;
vignette4_mc.load( new URLRequest( datas.IMG[3].@urlimage_min ) );
curseur_mc.mouseEnabled 	= false;
_timer.start();

addEventListener( MouseEvent.CLICK, _onClickVignette );
}

private function _onTimerComplete(e:TimerEvent = null):void
{
if(!_isTweening){
if( _currentIdx == 3){
_currentIdx = 0
}else{
_currentIdx++;
}
TweenNano.to( curseur_mc, .4 , {y:curseur_oriY + (_currentIdx * TAILLE_IMG), onComplete:_onTweenComplete});
_isTweening = true;
}
_timer.reset();
_timer.start();
}

private function _onClickVignette(e:MouseEvent):void {
if(e.target is Vignette){
var vignette:Vignette 	= (e.target as Vignette);
_currentIdx 			= vignette.id;
TweenNano.to( curseur_mc, .4 , {y:curseur_oriY + (_currentIdx * TAILLE_IMG), onComplete:_onTweenComplete});
_isTweening 			= true;
_timer.reset();
_timer.start();
}
}

private function _onTweenComplete():void {
animation_mc.gotoAndStop( _currentIdx + 1 );
_isTweening = false;
dispatchEvent( new Event( TWEEN_COMPLETE ) );
}

public function get currentIdx():int {
return _currentIdx;
}

}
}

Toutes les briques sont en place, reste juste à orchestrer tous ces objets pour qu’ils travaillent ensemble.


// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
// ::: TODO ::::::::::::::::::::::::::::::::::::::::::::::::::::
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//
// chargement du XML	 , OK
// chargement des images	 , OK
// lissage des images chargées	, OK
// menu + tempo	 , OK
// slider et mini-slider	 , OK
// mettre le timing en flashvars, OK
// vignette clickable	 , OK
// anim de preload	 , OK
// intégration	 , OK
// clip d'animation	 , OK
// optimisation	 , OK
// retour d'erreur
// sur le chargement du XML	, OK
// sur le chargement des images, OK
// faire la doc (tech/redac)	, OK
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
package
{
import flash.display.Sprite;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.filters.DropShadowFilter;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.display.StageScaleMode;
import flash.display.StageAlign;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.ui.ContextMenuBuiltInItems;
import flash.events.MouseEvent;
import flash.net.navigateToURL;
import flash.net.URLRequest;
import flash.display.StageQuality;
import com.greensock.TweenNano;

public class Main extends Sprite
{
private const DROPSHADOW:DropShadowFilter 	= new DropShadowFilter(6, 45, 0x666666, .6, 6, 6, .8, 3);

private const TEXT_MAXLENGTH_TITLE:int 		= 35;
private const TEXT_MAXLENGTH_BASELINE:int 	= 47;

private const COORDS_TITLE:Point			= new Point( 156, 32 );
private const COORDS_BASELINE:Point			= new Point( 169, 353 );

private	const VERSION:String 				= "0.00";

private const IMG_WIDTH:int					= 572;
private const IMG_HEIGHT:int				= 325;
private const MIN_WIDTH:int					= 95;
private const MIN_HEIGHT:int				= 95;

private const fmt_titre:TextFormat 			= new TextFormat;
private const fmt_descritptif:TextFormat 	= new TextFormat;
private const fmt_petit:TextFormat 			= new TextFormat;

private var animation_rect:Rectangle		= new Rectangle( 0, 0, IMG_WIDTH, IMG_HEIGHT );
private var cartouche_rect:Rectangle		= new Rectangle( 0, 0, MIN_WIDTH, MIN_HEIGHT );

private var TEMPORISATTION:int 				= 5000;	// flashVars

public var cartouche_mc:LulzSlider;
public var slider_mc:LulzSlider;
public var menu_mc:MenuSlider;

private var titre:TextField;
private var descriptif:TextField;
public var erreur_txt:TextField;

public function Main() {
fmt_titre.font = new EagleBold().fontName;
fmt_titre.size = 20;
fmt_titre.color= 0xFFFFFF;

fmt_descritptif.font = new EagleBold().fontName;
fmt_descritptif.size = 20;
fmt_descritptif.color= 0xFF9200;

fmt_petit.size 		= 4;

addEventListener( Event.ADDED_TO_STAGE, _onAddedToStage );
}

private function _onAddedToStage(e:Event):void
{
removeEventListener( Event.ADDED_TO_STAGE, _onAddedToStage );

stage.scaleMode 		 = StageScaleMode.NO_SCALE;
stage.align				 = StageAlign.TOP_LEFT;
stage.stageFocusRect 	 = false;
stage.quality			 = StageQuality.BEST;

// cache le menu
var newContextMenu:ContextMenu = new ContextMenu;
newContextMenu.hideBuiltInItems();

// ajout de 1 items persos
var cmi:ContextMenuItem = new ContextMenuItem("LulzSlider " + VERSION);
newContextMenu.customItems.push(cmi);
this.contextMenu 		= newContextMenu;

loader_mc.visible		= true;

// remplacé par des images avec le dropshadow pour les gains de perfs
//cartouche_mc.filters 	= [DROPSHADOW];

// remplacé par des images avec le dropshadow pour les gains de perfs
//menu_mc.filters 		= [DROPSHADOW];

// remplacé par des images avec le dropshadow pour les gains de perfs
//slider_mc.filters 	= [DROPSHADOW];

slider_mc				= new LulzSlider();
slider_mc.scrollRect	= new Rectangle( 0, 0, IMG_WIDTH, IMG_HEIGHT );
slider_mc.buttonMode 	= true;

cartouche_mc			= new LulzSlider();
cartouche_mc.scrollRect = new Rectangle( 0, 0, MIN_WIDTH, MIN_HEIGHT );

// chargement des datas
erreur_txt.embedFonts	= true;
erreur_txt.mouseEnabled = false;
erreur_txt.autoSize		= TextFieldAutoSize.LEFT;
//erreur_txt.opaqueBackground = 0xFFFFFF;
Config.instance.loadDatas( flashvar("cheminXML", "images.xml") );
Config.instance.addEventListener( Event.COMPLETE, init );
Config.instance.addEventListener( Event.CLOSE, _onError );
}

private function init( e:Event = null):void
{
// menu
menu_mc						= new MenuSlider;
menu_mc.x					= 9;
menu_mc.y					= 130;
menu_mc.init( int( flashvar("TEMPORISATTION", 5000) ) );
menu_mc.addEventListener( MenuSlider.TWEEN_COMPLETE  , _animateMe);

// cartouche
cartouche_mc.visible = false;
cartouche_mc.x			= 9;
cartouche_mc.y			= 6;
addChild( cartouche_mc );
cartouche_mc.largeur_image	= MIN_WIDTH;
cartouche_mc.hauteur_image	= MIN_HEIGHT;
cartouche_mc.addImage( Config.instance.datas.IMG[0].@urlimage_min );
cartouche_mc.addImage( Config.instance.datas.IMG[1].@urlimage_min );
cartouche_mc.addImage( Config.instance.datas.IMG[2].@urlimage_min );
cartouche_mc.addImage( Config.instance.datas.IMG[3].@urlimage_min );
cartouche_mc.addEventListener(Event.CLOSE, _onError);
cartouche_mc.loadAll();

// slider
slider_mc.visible = false;
slider_mc.x 			= 51;
slider_mc.y 			= 46;
addChild( slider_mc );
slider_mc.largeur_image		= IMG_WIDTH;
slider_mc.hauteur_image 	= IMG_HEIGHT;
slider_mc.addImage( Config.instance.datas.IMG[0].@urlimage_grand );
slider_mc.addImage( Config.instance.datas.IMG[1].@urlimage_grand );
slider_mc.addImage( Config.instance.datas.IMG[2].@urlimage_grand );
slider_mc.addImage( Config.instance.datas.IMG[3].@urlimage_grand );
slider_mc.addEventListener(Event.CLOSE, _onError);
slider_mc.addEventListener( Event.COMPLETE, _onStart );
slider_mc.loadAll();

addChild( erreur_txt );
}

private function _onStart(e:Event):void
{
var ombres_mc:OmbresMC = new OmbresMC;
ombres_mc.cacheAsBitmap= true;
ombres_mc.x			= 7;

slider_mc.visible 	= true;
cartouche_mc.visible= true;

// tri dynamique de la liste d'affichage
removeChild( loader_mc );
addChild( ombres_mc );
addChild( slider_mc );
addChild( cartouche_mc );
addChild( menu_mc );

titre 		= buildTextField(	Config.instance.datas.IMG[0].@titre,
COORDS_TITLE, TEXT_MAXLENGTH_TITLE,
fmt_titre,
0x000000);
addChild( titre );
descriptif	= buildTextField(	Config.instance.datas.IMG[0].@description,
COORDS_BASELINE, TEXT_MAXLENGTH_BASELINE,
fmt_descritptif,
0xFFFFFF);
addChild( descriptif );
addChild( erreur_txt );

// ecouteur
slider_mc.addEventListener(MouseEvent.CLICK, _onClickSlider );
}

private function _onError(e:Event):void
{
erreur_txt.appendText( (e.target).err );
}

private function _onClickSlider(e:MouseEvent):void
{
var req:URLRequest = new URLRequest( Config.instance.datas.IMG[menu_mc.currentIdx].@urlRedirection );
navigateToURL( req, "_blank" );
}

private function _animateMe(e:Event):void
{
setText(titre,
Config.instance.datas.IMG[menu_mc.currentIdx].@titre,
TEXT_MAXLENGTH_TITLE);
setText(descriptif,
Config.instance.datas.IMG[menu_mc.currentIdx].@description,
TEXT_MAXLENGTH_BASELINE);
TweenNano.to( animation_rect, .5 , {x:(menu_mc.currentIdx * IMG_WIDTH), onUpdate:MoveScrollRect});
TweenNano.to( cartouche_rect, .5 , {x:(menu_mc.currentIdx * MIN_WIDTH), onUpdate:MoveScrollRect});
}

private function MoveScrollRect():void
{
slider_mc.scrollRect	= animation_rect;
cartouche_mc.scrollRect	= cartouche_rect;
}

private function buildTextField( str:String, coords:Point, limit:int , fmt:TextFormat, bgColor:uint ):TextField
{
var tf:TextField 		= new TextField;
tf.defaultTextFormat	= fmt;
tf.embedFonts 			= true;
tf.mouseEnabled 		= false;
tf.multiline 			= true;
tf.background 			= true;
tf.backgroundColor 		= bgColor;

setText(tf, str, limit);

tf.autoSize 			= TextFieldAutoSize.LEFT;
tf.filters 				= [ DROPSHADOW ];
tf.rotation 			= -4.5;

tf.x 					= coords.x;
tf.y 					= coords.y;

tf.cacheAsBitmap		= true;

return tf;
}

private function setText( tf:TextField, newStr:String, limit:int ):void
{
var texte:String = "\n "+newStr+" \n ";
if(newStr.length > limit){
texte = texte.substr(0,limit) + " \n ";
}
if(limit == TEXT_MAXLENGTH_TITLE){
tf.text 				= texte.toUpperCase();
}else{
tf.text 				= texte;
}

tf.setTextFormat( fmt_petit, 0, 1 );
tf.setTextFormat( fmt_petit, texte.length-2, texte.length );
}

private function flashvar( nom:String, valeurParDefautSiInconnu:* ):*
{
if (!stage) {
return 0;
}
var result:* = valeurParDefautSiInconnu;
if( stage.loaderInfo.parameters[nom] != undefined ){
if( valeurParDefautSiInconnu is Number ){
result = Number( stage.loaderInfo.parameters[nom] );
} else {
result = stage.loaderInfo.parameters[nom];
}
}
return result;
}

}
}

 

Tests/Recette et mise en ligne
Pour un petit projet comme celui ci la recette se fait très rapidement, le code a été injecté dans la maquette donc l’intégration graphique est fidèle à 100%, ensuite au niveau technique c’est très simple aussi (les images se chargent les clicks fonctionnent).

Ce projet a prit environ 8 heures de travail, c’est beaucoup plus long que de réutiliser un diaporama tout fait, mais c’est un petit élément unique fabriqué sur mesure.
Un genre de luxe que la grande majorité des sites ne peut s’offrir ^^

J’ai livré le 30 juillet, la dead-line pour le développement était fixée au 12 août, il n’y a plus qu’a attendre sa mise en ligne.

Merci d’avoir lu jusqu’au bout :)



les commentaires sont fermés.