01 gennaio 2010

Esploriamo le novità di Flex 4 realizzando una galleria video XML

di Fabio Biondi

In questo articolo realizzeremo una galleria video allo scopo di esplorare alcune nuove funzionalità di Flex 4, ancora in versione beta e rilasciato teoricamente nel primo trimestre 2010: vedremo come skinnare i nuovi spark components, utilizzeremo le classi di layout e gli spark containers e sfrutteremo inoltre le nuove funzionalità degli "states" e degli itemRenderer di Flex4; applicheremo inoltre concetti già esistenti in Flex 3 ma sempre utilissimi, quali il dispatch di eventi personalizzati, il caricamento di dati XML (a tal fine  applicheremo il design pattern Singleton), utilizzeremo classi value objects, applicheremo transitions, css e includeremo fonts; creeremo quindi a tutti gli effetti una piccola appilcazione, anche se molto semplice, che copra diverse casistiche frequenti durante lo sviluppo di rich internet applications.

Visualizza la versione live
(clicca sull'swf con il tasto destro del mouse, e seleziona VIEW Source, per scaricare il codice sorgente)


La logica dell'applicazione è banale: visualizzeremo sulla sinistra una lista di video (rappresentati da un titolo, una thumbnail e una descrizione visibile al rollover) caricando i dati da un file XML. Al click di un elemento della lista verrà visualizzato il relativo video nel componente VideoPlayer.

Di seguito il mockup dell'applicazione, in questo contesto forse superfluo ma nel caso di applicazioni più complesse utilissimi per un confronto con il cliente o semplicemente per valutare meglio il layout e le funzionalità dell'applicazione (mockup realizzato con Balsamiq, un piccolo ma potente tool realizzato in AIR.




Il file mxml principale

Innanzitutto, dobbiamo creare la UI dell'applicazione e a tal fine utilizzeremo essenzialmente un componente List e un VideoPlayer (nuovo in Flex4), definendo inoltre un HorizontalLayout, una nuova modalità per definire il tipo di layout di un container (che in Flex 3 era prerogativa della proprietà "layout").

Al termine del caricamento dell'applicazione (evento applicationComplete) invochiamo il metodo loadData() della classe VideoLoader,  di tipo singleton, che creeremo allo scopo di gestire il caricamento dei dati XML e che effettuerà il dispatch dell'evento XMLEvent.LOADED al termine del caricamento, passando come parametro l'intero oggetto XML sotto forma di ArrayCollection (come vedremo nel paragrafo successivo).

Infine, vorrei farvi notare che il componente List gestisce un evento personalizzato chiamato "selected", dispatchato ogni qualvolta un utente selezionerà un nuovo elemento dalla lista video.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               xmlns:list="com.fabiobiondi.videoplayer.views.list.*"
               minWidth="950" minHeight="400"
               applicationComplete="init()"
               viewSourceURL="http://path/to/source"
               >
   
    <fx:Style source="css/styles.css" />
       
    <fx:Script>
        <![CDATA[
            import com.fabiobiondi.videoplayer.events.ItemEvent;
            import com.fabiobiondi.videoplayer.events.XMLEvent;
            import com.fabiobiondi.videoplayer.managers.VideoLoader;
            import com.fabiobiondi.videoplayer.skins.VerticalScrollbar;
           
            import spark.skins.spark.VideoPlayerSkin;
           
           
            /**
             * Init application loading assets from the XML
             *
             */
            private function init():void {
               
                VideoLoader.getInstance().loadData("data/video_electronics.xml")
                VideoLoader.getInstance().addEventListener(XMLEvent.LOADED, onLoadData);

            }
           
           
            /**
             * XML loading completed
             * The XMLEvent 'LOADED' is dispatched after all xml data are loaded
             */
            private function onLoadData(e:XMLEvent):void {
               
                videolist.dataProvider = e.videos;
               
            }
           
           
           
            /**
             * Load video on VideoPlayer
             * The ItemEvent is dispatched after an user list selection
             *
             * @param     e    ItemEvent
             */
            private function loadvideo(e:ItemEvent):void {
                player.source = e.video.videourl as String;
               
            }
               
        ]]>
    </fx:Script>
   
   
    <s:layout>
        <s:HorizontalLayout/>
    </s:layout>
   
    <!--video list-->
    <list:VideoList id="videolist" bottom="0" top="0" width="300"
                    selected="loadvideo(event)"/>
   
    <!--player video-->
    <s:VideoPlayer id="player"
                   width="100%" height="100%"
                   minWidth="200" minHeight="200"
                  />
   
</s:Application>

Il file XML
<list>
    <video id="1">
        <title>Wii NunChuck controller, Arduino and servos</title>
        <thumb>assets/electronics/wiinunchukservo.jpg</thumb>
        <description>The Wii nunchuck controller contains a 3 axis accelerometer, one joystick ...</description>
        <videourl>http://www.fabiobiondi.com/blog/wp-content/uploads/2009/12/09122009093.flv</videourl>
        <blogurl>http://www.fabiobiondi.com/blog/2009/12/wii-nunchuck-controller-and-arduino/</blogurl>
    </video>
    ...
</list>

Classe VideoLoader.as

Dato che il caricamento dati dovrà essere effettuato un unica volta ho deciso di implementare il design pattern singleton e di scrivere una classe che si occupi esclusivamente di questa operazione.
Al termine del caricamento effettuiamo un ciclo sull'intero oggetto creando un nuovo array i cui elementi saranno dei value object di tipo VideoItem (che vedremo nel prossimo paragrafo) contenenti le informazioni di ogni singolo video (id, titolo, descrizione, url, ecc.). L'utilizzo di value object non è indispensabile ma diventa tale, già dai tempi di Flex 3, nel caso in cui volessimo evitare i fastidiosi messaggi di warning visualizzati nel pannello console di Flex quando si "bindano" delle proprietà all'interno di un item renderer.
Quindi, effettuaimo il dispatch del nostro evento personalizzato XMLEvent.LOADED passando come parametro il nuovo ArrayCollection, pronto per essere utilizzato nel nostro file mxml principale per il popolamento del componente List.

Infine,  è importare notare come la classe VideoLoader debba implementare l'interfaccia IEventDispatcher (e i relativi metodi) al fine di poter dispatchare eventi.

package com.fabiobiondi.videoplayer.managers
{
    import com.fabiobiondi.videoplayer.events.XMLEvent;
    import com.fabiobiondi.videoplayer.model.entities.VideoItem;
   
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
   
    import mx.collections.ArrayCollection;
   

    public class VideoLoader implements IEventDispatcher
    {
       
        private var _data:XML;
        private var _urlLoader:URLLoader;
        private var dispatcher:EventDispatcher;
       
        private static var _instance:VideoLoader;
        public static const LOADED:String = "loaded";
       
        public function VideoLoader(singleton:SingleTon)
        {
            dispatcher = new EventDispatcher(this);
           
        }
               
        /**
         * Return a messageLoader istance
         * @return MessageLoader
         */
        public static function getInstance():VideoLoader
        {
            if (VideoLoader._instance == null) {
                VideoLoader._instance = new VideoLoader(new SingleTon());
            }
           
            return VideoLoader._instance;
           
        }
       
       
        public function loadData(xmlurl:String):void {
           
            var urlRequest:URLRequest = new URLRequest(xmlurl)
            _urlLoader = new URLLoader();
            _urlLoader.addEventListener(Event.COMPLETE, onXMLLoaded)
            _urlLoader.load(urlRequest);
           
        }
       
       
        private function onXMLLoaded(e:Event):void {
            _data = XML(_urlLoader.data);
           
            var _videos:Array = new Array();
           
            // Loop over the received XML object
            for (var i:uint = 0; i < _data.video.length(); i++) {
               
                var xmlNode:XML = _data.video[i];
               
                // Create an object for each node (using the value object class "VideoItem"
                var videoVO:VideoItem = new VideoItem();
                videoVO.id = xmlNode.@id;
                videoVO.title = xmlNode.title;
                videoVO.thumb  = xmlNode.thumb;
                videoVO.description = xmlNode.description;
                videoVO.videourl = xmlNode.videourl;
                videoVO.blogurl = xmlNode.blogurl;
               
                _videos.push(videoVO)
            }
           
            dispatchEvent(new XMLEvent(XMLEvent.LOADED, new ArrayCollection(_videos)));
           
           
        }
       
       
        public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void{
            dispatcher.addEventListener(type, listener, useCapture, priority);
        }
       
        public function dispatchEvent(evt:Event):Boolean{
            return dispatcher.dispatchEvent(evt);
        }
       
        public function hasEventListener(type:String):Boolean{
            return dispatcher.hasEventListener(type);
        }
       
        public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void{
            dispatcher.removeEventListener(type, listener, useCapture);
        }
       
        public function willTrigger(type:String):Boolean {
            return dispatcher.willTrigger(type);
        }

       
    }

}

class SingleTon{}


Classe VideoItem.as

Di seguito la classe ValueObject per contenere le informazioni di un singolo video, utilizzata nell'esempio precedente.

package com.fabiobiondi.videoplayer.model.entities
{
    [Bindable]
    public class VideoItem
    {
       
        private var _id:Number;
        private var _thumb:String;
        private var _title:String;
        private var _description:String;
        private var _videourl:String;
        private var _blogurl:String;
       
       
        public function VideoItem()
        {
        }
       

        public function get id():Number
        {
            return _id;
        }
       
        public function set id(value:Number):void
        {
            _id = value;
        }
       
       
       

        public function get title():String
        {
            return _title;
        }

        public function set title(value:String):void
        {
            _title = value;
        }
   
       
        public function get description():String
        {
            return _description;
        }
       
        public function set description(value:String):void
        {
            _description = value;
        }

       
       
       

        public function get thumb():String
        {
            return _thumb;
        }

        public function set thumb(value:String):void
        {
            _thumb = value;
        }

       
        public function get videourl():String
        {
            return _videourl;
        }
       
        public function set videourl(value:String):void
        {
            _videourl = value;
        }
       
        public function get blogurl():String
        {
            return _blogurl;
        }
       
        public function set blogurl(value:String):void
        {
            _blogurl = value;
        }
       
    }
}




XMLEVENT.as

Di seguito il codice dell'evento personalizzato XMLEvent con la gestione di un unico parametro, l'ArrayCollection contenente la lista completa dei video.

package com.fabiobiondi.videoplayer.events
{
   
 
  import flash.events.Event;
 
  import mx.collections.ArrayCollection;
 
  public class XMLEvent extends Event
  {
     
    //public static const COMING_MESSAGE:String = "update"
    public static const LOADED:String = "loaded"
   
    public var videos:ArrayCollection;
   
    public function XMLEvent( _type:String, videolist:ArrayCollection):void
    {
     
        super(_type);
        this.videos = videolist;
    }
   
   
    override public function clone():Event
    {
       return new XMLEvent(type, videos);
    }
  }
}




LIST COMPONENT

Vediamo ora il componente List utilizzato nel file mxml principale.

E' un banale mxml custom component basato sulla classe List che oltre a settare alcune proprietà di default definisce l'evento myChangedHandler() invocato ogni qualvolta un utente seleziona un elemento della lista.
Dopo ogni selezione effettuiamo quindi il dispatch di un altro evento personalizzato, chiamato ItemEvent.SELECTED ( non ricopio la classe perchè molto simile a XMLEvent.as ma è ovviamente disponibile nel codice sorgente) passando come parametro il value object Videoitem, contenente le informazioni del video selezionato.
Se ricordate, il componente List istanziato nel file mxml principale, gestiva proprio questo evento ("selected") per mandare in esecuzione i video dopo ogni selezione.

Infine, per visualizzare gli elementi della lista in modo appropriato e creare le animazioni di rollover implementiamo l'itemRenderer Videoitem, che esploreremo nel prossimo paragrafo.

<?xml version="1.0" encoding="utf-8"?>
<s:List xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx" width="300" height="100%"
        itemRenderer="com.fabiobiondi.videoplayer.views.list.itemRenderer.Videoitem"
        change="myChangedHandler(event);"
        allowMultipleSelection="false"
        buttonMode="true"
        >
   
    <fx:Metadata>
        [Event(name="selected", type="com.fabiobiondi.videoplayer.events.ItemEvent")]
    </fx:Metadata>
   
    <s:layout>
        <s:VerticalLayout gap="1"/>
    </s:layout>
   
   
    <fx:Script>
        <![CDATA[
            import com.fabiobiondi.videoplayer.events.ItemEvent;
            import com.fabiobiondi.videoplayer.model.entities.VideoItem;
            import com.fabiobiondi.videoplayer.views.list.itemRenderer.Videoitem;
           
            import spark.events.IndexChangeEvent;
           
            private function myChangedHandler(event:IndexChangeEvent):void {
               
                var videoVO:VideoItem = new VideoItem();
                videoVO.id = event.currentTarget.selectedItem.id;
                videoVO.title = event.currentTarget.selectedItem.title;
                videoVO.thumb  = event.currentTarget.selectedItem.thumb;
                videoVO.description = event.currentTarget.selectedItem.description;
                videoVO.videourl = event.currentTarget.selectedItem.videourl;
                videoVO.blogurl = event.currentTarget.selectedItem.blogurl;
               
               
                dispatchEvent(new ItemEvent(ItemEvent.SELECTED, videoVO ));
            }
               
   
        ]]>
    </fx:Script>

   
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
   
   
</s:List>



L'ITEM RENDERER del componente List

In Flex 4, il concetto di itemRenderer è identico alla versione 3 ma si può implementare in modalità differenti grazie all'architettura degli states del nuovo SDK.

Quindi, in questo itemRenderer:

1) gestiamo gli states normal, hovered e selected, invocati automaticamente ogni qualvolta l'evento relativo viene dispatchato.
2) definiamo transitions e relativi eventi, allo stesso modo in cui facevamo in Flex3. Questo task non è necessario ma è volta a migliorare la user experience
3) stabiliamo quali componenti visualizzare definendo le loro proprietà in relazione ad ogni stato. Anche questa è una nuova interessante funzionalità offerta da Flex4 che permette di definire il valore di una proprietà in ogni stato.
Ad esempio il seguente codice stabilisce che il componente 'thumb' sarà visibile nello stato di default ('normal') e selezionato ('selected') ma sarà nascosto nello stato rollover ('hovered')

<mx:Image id="thumb"
        visible.normal="true"
        visible.hovered="false"
        visible.selected="true"
         />
         

Il codice completo dell'itemRenderer:

<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
                xmlns:s="library://ns.adobe.com/flex/spark"
                xmlns:mx="library://ns.adobe.com/flex/mx"
                width="300" height="120" 
                autoDrawBackground="true">
   
   
    <!--item renderer states-->
    <s:states>
        <s:State name="normal"/>
        <s:State name="hovered"/>
        <s:State name="selected"/>
    </s:states>
   
   
    <!-- State Transitions -->
    <s:transitions>
        <s:Transition fromState="normal" toState="hovered">
            <s:Parallel effectEnd="labelContainer.alpha = 0">
                <s:Wipe direction="down" target="{this}" duration="350"/>
                <s:Fade target="{thumb}" duration="1000" />
            </s:Parallel>
        </s:Transition>
        <s:Transition  fromState="*" toState="normal">
            <s:Sequence effectStart="labelContainer.alpha = 1">
                <s:Wipe direction="up" target="{this}" duration="350"/>
                <s:Fade target="{thumb}" duration="450" />
                <s:Wipe direction="right" target="{labelContainer}" duration="350"/>
            </s:Sequence>
        </s:Transition>
       
        <s:Transition  fromState="selected" toState="*">
            <s:Sequence effectStart="labelContainer.alpha = 1">
                <s:Fade target="{thumb}" duration="450" />
            </s:Sequence>
        </s:Transition>
       
    </s:transitions>
   
   
   
    <!--background-->
    <s:Rect left="0" right="0" top="0" bottom="0" >
        <s:fill>
            <s:SolidColor
                color.normal="0x000000"
                color.hovered="0x000000"
                color.selected="0x424242"
                />
        </s:fill>
    </s:Rect>
   
   
   
    <!--Thumbnail-->
    <mx:Image id="thumb" width="100%" height="100%"
        source.normal="{data.thumb}"
        source.hovered=""
        visible.normal="true"
        visible.hovered="false"
        visible.selected="true"
         />
   
   
    <!--Title Container-->
    <s:Group id="labelContainer" left="-10" top="80"
             visible.normal="true"
             visible.hovered="false"
             visible.selected="false">
       
        <s:Rect width="{labelTxt.width}" height="35" radiusX="5" radiusY="5" >
            <s:fill>
                <s:SolidColor color="0x000000" alpha="0.5" />
            </s:fill>
        </s:Rect>
       
        <s:Label id="labelTxt"
                 text.normal="{data.title}"
                 fontWeight.selected="bold"
                 fontSize="12"
                 verticalCenter="0"
                 paddingLeft="15" paddingTop="3"
                 width="250" height="30" />
       
    </s:Group>
   
    <!--Description-->
    <s:Label id="descTxt"
             text="{data.description}"
             fontSize="11"
             verticalCenter="0"
             paddingLeft="10" paddingTop="10" paddingRight="20"
             width="100%" height="100%"
             visible.normal="false"
             visible.selected="true"
             visible.hovered="true"/>
   
   
</s:ItemRenderer>


SKINNING

Come vedremo di seguito, in Flash builder 4, l'implementazione  dei CSS è pressochè rimasta invariata anche se cambiano diversi stili (ovviamente documentati nella API reference dell'sdk).
Innanzitutto embeddiamo (ovvero includiamo) il font ARIAL al fine di applicare l'antialias (è una scelta assolutamente personale).
Dopo di che definiamo le classi per lo skinning delle scrollbar e del componente videoplayer(opzione disponibile anche in FX3 ma la cui implementazione era decisamente differente).  Inoltre, dobbiamo definire i namespaces per le librarie mx e spark, in modo da poterle utilizzare entrambe.

NOTA: Flash Catalyst sarà di grande aiuto e supporto per quanto riguarda queste operazione di skinning e realizzerà "automaticamente" la maggior parte del codice.
Software come Photoshop CS4/5 o Illustrator CS4/5 possono inoltre già esportare i propri files in formato FXG,  totalmente supportato da Flex 4 e integrabile con una semplice operazione di copia-incolla.
Questi argomenti sono però off-topic e verranno trattati sicuramente in un articolo dedicato.

/* CSS file */
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

@font-face {
    src: url("assets/fonts/ARIAL.TTF");
    fontFamily: myFontFamily;
    advancedAntiAliasing: true;
    embedAsCFF: true;
}

s|Application {
    fontFamily: myFontFamily;
    fontSize: 15;
    color: #ffffff;
    background-color: #000000;
}


s|List s|Scroller {
    horizontalScrollPolicy: off;
    verticalScrollPolicy: auto;
}

s|List s|VScrollBar {
    /*    This skin class is taken from http://theflashblog.com/?p=1063*/
    skin-class: ClassReference("com.fabiobiondi.videoplayer.skins.VerticalScrollbar");   
}

s|VideoPlayer {
    color: #ffffff;
    baseColor: haloOrange;
    skin-class: ClassReference("com.fabiobiondi.videoplayer.skins.VideoPlayerCustomSkin");   
   
}

SCROLLBAR SKIN

Per skinnare le scrollbars dobbiamo implementare e modificare tre classi, ognuna relativa ad una parte di componente.
In tutta onesta, ne ho trovata una molto carina su FlashBlog e l'ho utilizzata:
http://theflashblog.com/?p=1063
Ho copiato le tre classi nella mia cartella /skin e le ho usate all'interno del mio css.
Il codice è comunque molto semplice e facilmente modificabile.


VerticalScrollbar.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:ai="http://ns.adobe.com/ai/2008" minWidth="15" minHeight="150">
    <s:states>
        <s:State name="normal"/>
        <s:State name="disabled"/>
        <s:State name="inactive"/>
    </s:states>
    <fx:Metadata>[HostComponent("spark.components.Scroller")]</fx:Metadata>
    <s:Button left="0" right="0" top="0" bottom="0" width="15" skinClass="com.fabiobiondi.videoplayer.skins.VScrollTrack" id="track"/>
    <s:Button skinClass="com.fabiobiondi.videoplayer.skins.VScrollThumb" id="thumb"/>
</s:Skin>




VScrollThumb.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:ai="http://ns.adobe.com/ai/2008" xmlns:d="http://ns.adobe.com/fxg/2008/dt" xmlns:lib="Thumb1_library.*" xmlns:fc="http://ns.adobe.com/thermo/2009">
    <s:states>
        <s:State name="up"/>
        <s:State name="over"/>
        <s:State name="down"/>
        <s:State name="disabled"/>
    </s:states>
    <fx:Metadata>[HostComponent("spark.components.Button")]</fx:Metadata>
    <s:Rect minHeight="50" width="15" left="0" right="0" top="0" bottom="0">
        <s:fill>
            <s:SolidColor color="0x545454" color.over="0x333333" color.down="#242424"/>
        </s:fill>
        <s:stroke>
            <s:SolidColorStroke color="0x0c0d0d" caps="none" weight="1" joints="miter" miterLimit="4"/>
        </s:stroke>
    </s:Rect>
    <s:Rect left="1" right="1" top="1" bottom="1">
        <s:stroke>
            <s:SolidColorStroke color="0x262626" caps="none" weight="1" joints="miter" miterLimit="4"/>
        </s:stroke>
    </s:Rect>
</s:Skin>





VScrollTrack.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:ai="http://ns.adobe.com/ai/2008">
    <s:states>
        <s:State name="up"/>
        <s:State name="over"/>
        <s:State name="down"/>
        <s:State name="disabled"/>
    </s:states>
    <fx:Metadata>[HostComponent("spark.components.Button")]</fx:Metadata>
    <s:Rect left="0" right="0" top="0" width="15" bottom="0">
        <s:fill>
            <s:SolidColor color="0x222222"/>
        </s:fill>
        <s:stroke>
            <s:SolidColorStroke color="0x222222" caps="none" weight="1" joints="miter" miterLimit="4"/>
        </s:stroke>
    </s:Rect>
</s:Skin>





Skinnare il VideoPlayer di Flex 4


Dato che la skin di default del componente VideoPlayer è bianca e che nella  demo che stiamo realizzando abbiamo utilizzato prevalemente dei colori scuri dovremmo skinnare anche questo componente al fine di rendere l'applicazione esteticamente più gradevole ; )

Google non mi è stato d'aiuto e in questi casi l'unico modo per capire come procedere è l'analisi dell'SDK di flex.
Dopo una breve e facile ricerca ho trovato la classe di default utilizzata da Flex 4 per lo skinning del video player, chiamata VideoPlayerSkin.mxml e
ho quindi creato un nuovo file mxml per la mia skin ricopiandone il contenuto.

Analizzandone il codice ho notato che questa classe mi permette di definire pochi elementi a me utili, tra i quali i colori della ScrubBar, ovvero la barra centrale del video player che contiene la progress bar, ma che per cambiare i colori dei pulsanti (play, fullscreen, ecc.) o della barra del volume avrei dovuto modificare altre 6 o 7 classi.
In precedenza, avevo però notato che in modalità fullscreen, la barra comandi del video player si presentava già di colore scuro e ho pensato quindi di sfruttare quella skin, dato che da qualche parte doveva pur esser....

Quindi? Guardando il codice della classe originale, VideoPlayerSkin, ho notato che per ogni elemento della displayList definisce due diverse skin, una per lo stato di default (skinClass) e uno per la modalità fullscreen (skinClass.fullScreenStates)

Per risolvere l'enigma, è stato sufficiente utilizzare il path della skin fullScreenStates anche per la modalità normale, in modo da utilizzare in entrambi i casi la versione "dark" (vedi esempio sottostante).

Prima:

skinClass="spark.skins.spark.mediaClasses.normal.VolumeBarSkin"
skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"



Dopo:

skinClass="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"



In questo modo, la skin del mio videoplayer sarà sempre di colore scuro.
Di seguito il codice finale (sembra complessa ma in realtà la maggior parte del lavoro è stata fatta da Adobe ; )

<?xml version="1.0" encoding="utf-8"?>

<!--

ADOBE SYSTEMS INCORPORATED
Copyright 2008 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: Adobe permits you to use, modify, and distribute this file
in accordance with the terms of the license agreement accompanying it.

-->

<!--- The default skin class for the Spark VideoPlayer component. 

@langversion 3.0
@playerversion Flash 10
@playerversion AIR 1.5
@productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
             xmlns:fb="http://ns.adobe.com/flashbuilder/2009" alpha.disabledStates="0.5"
             chromeColor.normalStates="{undefined}" chromeColor.fullScreenStates="0xCCCCCC">
    <!-- chrome color of undefined in the normal states means we inherit the chromeColor property, and
    a chrome color of 0xCCCCCC in the fullScreenStates means we ignore the chromeColor property
    all together as 0xCCCCCC is essentially just a no-op color transform -->
   
    <!-- host component -->
    <fx:Metadata>
        /**
        * @copy spark.skins.spark.ApplicationSkin#hostComponent
        */
        [HostComponent("spark.components.VideoPlayer")]
    </fx:Metadata>
   
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized. */
            static private const exclusions:Array = ["videoDisplay", "playPauseButton", "scrubBar",
                "currentTimeDisplay", "timeDivider", "durationDisplay",
                "volumeBar", "fullScreenButton"];
           
            /**
             * @private
             */
            override protected function initializationComplete():void
            {
                useChromeColor = true;
                super.initializationComplete();
            }
           
            /**
             * @private
             */
            override public function get colorizeExclusions():Array
            {
                return exclusions;
            }
        ]]>
       
    </fx:Script>
   
    <!-- states -->
    <s:states>
        <s:State name="uninitialized" stateGroups="uninitializedStates, normalStates" />
        <s:State name="loading" stateGroups="loadingStates, normalStates" />
        <s:State name="ready" stateGroups="readyStates, normalStates" />
        <s:State name="playing" stateGroups="playingStates, normalStates" />
        <s:State name="paused" stateGroups="pausedStates, normalStates" />
        <s:State name="buffering" stateGroups="bufferingStates, normalStates" />
        <s:State name="playbackError" stateGroups="playbackErrorStates, normalStates" />
        <s:State name="disabled" stateGroups="disabledStates, normalStates"/>
        <s:State name="uninitializedAndFullScreen" stateGroups="uninitializedStates, fullScreenStates" />
        <s:State name="loadingAndFullScreen" stateGroups="loadingStates, fullScreenStates" />
        <s:State name="readyAndFullScreen" stateGroups="readyStates, fullScreenStates" />
        <s:State name="playingAndFullScreen" stateGroups="playingStates, fullScreenStates" />
        <s:State name="pausedAndFullScreen" stateGroups="pausedStates, fullScreenStates" />
        <s:State name="bufferingAndFullScreen" stateGroups="bufferingStates, fullScreenStates" />
        <s:State name="playbackErrorAndFullScreen" stateGroups="playbackErrorStates, fullScreenStates" />
        <s:State name="disabledAndFullScreen" stateGroups="disabledStates, fullScreenStates"/>
    </s:states>
   
    <!-- drop shadow -->
    <!--- @private -->
    <s:RectangularDropShadow id="shadow" blurX="17" blurY="17" alpha="0.32" distance="4"
                             angle="90" color="#131313" left="0" top="0" right="0" bottom="0"/>
   
    <!--- Video and player controls are clipped if they exceed the size of the
    component, but the drop shadow above is not clipped and sizes to the component.
    We also set verticalScrollPosition so that when we do clip, rather than clipping 
    off the bottom first, we clip off the top fist.  This is so the player controls
    are still visible when we start clipping. -->
    <s:Group id="clippedGroup" clipAndEnableScrolling="true" left="0" top="0" right="0" bottom="0"
             verticalScrollPosition="{Math.max(0, 184-clippedGroup.height)}">
       
        <!-- There's a minimum size for the video and controls.  If we go below that
        we are clipped. -->
        <s:Group minWidth="263" minHeight="184" left="0" right="0" top="0" bottom="0">
           
            <!-- background when the videoDisplay doesn't fill its whole spot -->
            <s:Rect bottom="1" left="1" right="1" top="1">
                <s:fill>
                    <s:SolidColor color="0x000000" />
                </s:fill>
            </s:Rect>
           
            <!--- @copy spark.components.VideoPlayer#videoDisplay -->
            <s:VideoDisplay id="videoDisplay" bottom="24" left="1" right="1" top="1" />
           
            <!-- video player controls -->
            <s:Group left="0" right="0" height="24" bottom="0" bottom.fullScreenStates="150">
               
                <!-- actual controls with a maxWidth in non-fullScreen mode -->
                <!--- @copy spark.components.VideoPlayer#playerControls -->
                <s:Group bottom="0" horizontalCenter="0" left="0" right="0" maxWidth.fullScreenStates="755" id="playerControls">
                   
                    <!--- @copy spark.components.VideoPlayer#playPauseButton -->
                    <s:ToggleButton id="playPauseButton" left="0" bottom="0"
                                    skinClass="spark.skins.spark.mediaClasses.fullScreen.PlayPauseButtonSkin"
                                    skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.PlayPauseButtonSkin"
                                    focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
                   
                    <!-- scrubbar + the currentTime/duration labels -->
                    <s:Group left="39" right="75" top="0" bottom="0">
                       
                        <!-- background for scrubbar + the currentTime/duration -->
                        <s:Rect left="0" right="0" top="0" bottom="0">
                            <s:fill>
                                <s:LinearGradient rotation="90">
                                    <s:GradientEntry color="0x000000" color.fullScreenStates="0x000000" alpha.fullScreenStates="0.55"/>
                                    <s:GradientEntry color="0x000000" color.fullScreenStates="0x000000" alpha.fullScreenStates="0.55"/>
                                </s:LinearGradient>
                            </s:fill>
                        </s:Rect>
                       
                        <!-- fill highlight  (exclude in fullScreen) -->
                        <s:Rect left="1" right="1" top="1" height="11" excludeFrom="fullScreenStates">
                            <s:fill>
                                <s:SolidColor color="0xFFFFFF" alpha="0.3" />
                            </s:fill>
                        </s:Rect>
                       
                        <!-- one pixel border -->
                        <s:Rect left="1" right="1" top="1" bottom="1">
                            <s:stroke>
                                <s:LinearGradientStroke weight="1" rotation="90">
                                    <s:GradientEntry color="0x666666" color.fullScreenStates="0x666666" alpha.fullScreenStates="0.12" />
                                    <s:GradientEntry color="0x666666" color.fullScreenStates="0x666666" alpha.fullScreenStates="0.09" />
                                </s:LinearGradientStroke>
                            </s:stroke>
                        </s:Rect>
                       
                        <!-- border for the scrubbar/time label controls -->
                        <s:Rect left="-1" right="0" top="0" bottom="0">
                            <s:stroke>
                                <s:SolidColorStroke color="0x131313" color.fullScreenStates="0x222222" alpha.fullScreenStates="0.66"  />
                            </s:stroke>
                        </s:Rect>
                       
                        <!-- scrub bar + currentTime/duration in a HorizontalLayout -->
                        <s:Group left="0" right="0" height="23" bottom="0">
                            <s:layout>
                                <s:HorizontalLayout verticalAlign="middle" gap="1" />
                            </s:layout>
                           
                            <!-- spacer -->
                            <s:Rect width="7" height="1" />
                           
                            <!--- @copy spark.components.VideoPlayer#scrubBar -->
                            <s:ScrubBar id="scrubBar" width="100%" liveDragging="true"
                                        skinClass="spark.skins.spark.mediaClasses.fullScreen.ScrubBarSkin"
                                        skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.ScrubBarSkin" />
                           
                            <!-- spacer -->
                            <s:Rect width="8" height="1" />
                           
                            <!--- @copy spark.components.VideoPlayer#currentTimeDisplay -->
                            <s:Label id="currentTimeDisplay" color.fullScreenStates="0xFFFFFF" />
                           
                            <s:Label text="/" color.fullScreenStates="0xFFFFFF" />
                           
                            <!--- @copy spark.components.VideoPlayer#durationDisplay -->
                            <s:Label id="durationDisplay" color.fullScreenStates="0xFFFFFF" />
                           
                            <!-- spacer -->
                            <s:Rect width="8" height="1" />
                        </s:Group>
                       
                    </s:Group>
                   
                    <!--- @copy spark.components.VideoPlayer#volumeBar -->
                    <s:VolumeBar id="volumeBar" snapInterval=".01" stepSize=".01" liveDragging="true"
                                 right="37" bottom="0"
                                 skinClass="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
                                 skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.VolumeBarSkin"
                                 focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
                   
                    <!--- @copy spark.components.VideoPlayer#fullScreenButton -->
                    <s:Button id="fullScreenButton" right="0" bottom="0" label="Fullscreen"
                              skinClass="spark.skins.spark.mediaClasses.fullScreen.FullScreenButtonSkin"
                              skinClass.fullScreenStates="spark.skins.spark.mediaClasses.fullScreen.FullScreenButtonSkin"
                              focusIn="event.target.depth=1" focusOut="event.target.depth=0" />
                   
                </s:Group>
               
            </s:Group>
           
            <!-- border -->
            <s:Rect left="0" right="0" top="0" bottom="0">
                <s:stroke>
                    <s:SolidColorStroke color="0x131313" />
                </s:stroke>
            </s:Rect>
           
        </s:Group>
    </s:Group>
</s:SparkSkin>



Visualizza il risultato finale
(clicca sull'swf con il tasto destro del mouse, e seleziona VIEW Source, per scaricare il codice sorgente)


CONCLUSIONE

Se Flex 3 era a mio avviso un gran bel prodotto, Flex 4 è decisamente migliorato con un occhio di riguardo finalmente anche al lato design e non solo a quello deveoper.
Ci sono moltissime nuove classi, situazioni da studiare e approfondire ma sembra decisamente che abbiano risolto moltissimi problemi noiosi del passato su diversi fronti, soprattutto riguardo lo skinning, anche grazie al supporto del formato FXG e l'integrazione con Flash Catalyst (anche se ho decisamente paura che la prima versione sarà scarna e poco pratica).

Le novità sono comunque moltissime e ovviamente impossibili da riassumere in un unico articolo.

Vota

Argomenti correlati:

2 settembre 2009
Flash Builder 4: finalmente disponibile un VideoPlayer completo

Nella prossima versione di Flex, Flash Builder 4 che uscirà nella prima metà del 2010, saranno disponibili moltissimi nuovi componenti. Tra i più richiesti e gettonati spicca il componente VideoPlayer che finalmente comprende tutte le classiche funzionalità disponibili da tempo in Flash attraverso il componente FLV Playback (play/pause, progressBar, timing, fullscreen, ecc).

3 settembre 2009
Eliminare i messaggi di “warning” di Flex

Problema:  quando effettuate un binding alla proprietà data all’interno di un itemRenderer ricevete il seguente messaggio nella console di Flex

Soluzione: Utilizzare una classe Value Object per popolare il data Provider del componente utilizzato

10 settembre 2009
Cambiare l’itemRenderer di un component a runtime (FLEX 3)

Buona parte dei componenti Flex supporta la proprietà itemRenderer, che permette di creare layout personalizzati per i loro elementi, evitando quindi di usare quello di default (che solitamente è un semplice testo)
Utilizzeremo un componente List per visualizzare i contenuti dell’RSS Tutorial di Actionscript.it e definiremo tre itemRenderer per creare tre differenti visualizzazioni dei contenuti.

12 ottobre 2009
Galleria di immagini in Flash Cs4 e Papervion 3D


In questo articolo realizzeremo una galleria di immagini visualizzate in un contesto tridimensionale utilizzando Flash Cs4, ActionScript 3.0, Papervision 3D e la libreria Tweener Caurina.

Comments