Fx4 IV: Spark containers

En este nuevo artículo veremos los nuevos containers de Fx4, los spark containers. Aunque Fx4 también proporciona los Halo containers, Adobe recomienda que usemos Spark containers siempre que sea posible, debido a que la arquitectura ha sido mejorada y optimizada. Como ejemplos de esta optimización tenemos que para mejorar el rendimiento de las aplicaciones, algunos Spark containers no permiten skinning y otro ejemplo sería que los Spark containers permiten el intercambio de layouts.

Tipos de Spark containers

Tenemos los siguientes tipos:

  • Group and DataGroup
  • SkinnableContainer, SkinnableDataContainer, Panel y Application (application no se explicará, ya que no varía mucho de Halo)

Diferencias básicas entre containers

Cada uno de los containers tiene un propósito concreto, a continuación lo comentamos:

  • Group y DataGroup nos permiten simplemente albergar unos hijos con un layout determinado. Usaremos SkinnableContainer, SkinnableDataContainer y Panel cuando queramos albergar componentes pero además sea necesario hacer un custom skin para el container.
  • Group y SkinnableContainer pueden contener cualquier componente visual. Los componentes visuales implementan IVisualElement y son subclases de DisplayObject, como las clases UIComponent o GraphicElement. Todos los componentes Spark y Halo implementan IVisualElement.
  • DataGroup y SkinnableDataContainer están optimizados para contener data items como String o Number, o elementos más complejos como XMLNode, por ejemplo.
  • En la imagen que sigue podemos ver las características de los diferentes containers:
    diagram1.png

    Veamos dos ejemplos de utilización de spark containers:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Application xmlns:s="library://ns.adobe.com/flex/spark">
    3.     <s:SkinnableContainer>
    4.           <s:layout><s:VerticalLayout/></s:layout>
    5.         <s:Button label="Button 1"/>
    6.         <s:Line xFrom="0" xTo="100">
    7.             <s:stroke>
    8.                 <s:LinearGradientStroke weight="2"/>
    9.             </s:stroke>
    10.         </s:Line>
    11.         <s:Button label="Button 2"/>
    12.     </s:SkinnableContainer>
    13. </s:Application>

    En el ejemplo vemos el uso de un SkinnableContainer. El resultado sería el siguiente:
    im11.png

    En el siguiente ejemplo se usa SkinnableDataContainer, recordemos que está optimizado para contener, sobre todo, data items:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Application
    3.     xmlns:fx="http://ns.adobe.com/mxml/2009"
    4.     xmlns:s="library://ns.adobe.com/flex/spark"
    5.     xmlns:mx="library://ns.adobe.com/flex/halo"
    6.     >
    7.     <s:SkinnableDataContainer
    8.         itemRenderer="spark.skins.default.DefaultItemRenderer"
    9.         >
    10.         <mx:ArrayCollection>
    11.             <fx:String>Made</fx:String>
    12.             <fx:String>In</fx:String>
    13.             <fx:String>Flex</fx:String>
    14.         </mx:ArrayCollection>
    15.     </s:SkinnableDataContainer>
    16. </s:Application>

    SkinnableDataContainer y DataGroup necesitan un itemRenderer, si no necesitamos uno en especial, podemos especificar el que se ve en el ejemplo. Si no especificamos itemRenderer, no aparecerá nada en pantalla. El resultado del código anterior es:
    im01.png

    Layouts

    No profundizaremos en el tema de layouts porque dedicaremos una sección exclusiva para ello; sólo lo veremos muy por encima.
    Todos los Spark containers tienen definido un layout por defecto, pero podemos especificar el que queramos. Los diferentes layouts que nos ofrece Fx4 son: BasicLayout (posicionamiento absoluto), HorizontalLayout ( posicionamiento horizontal), VerticalLayout (posicionamiento vertical) y TileLayout (posicionamiento en filas y columnas).

    Visión detallada de los spark containers

    Group y SkinnableContainer
    son containers pensados para albergar componentes que implementen la interface IVisualElement. La diferencia mayor entre estos dos containers es que SkinnableContainer puede ser skineado; Group no, ya que está pensado para una mínima carga de memoria.
    El layout básico del container Group es BasicLayout. El layout por defecto de SkinnableContainer es VerticalLayout.

    No entraremos en profundidad en el tema de styling y skinning porque tambien se dedicará un artículo, aunque a continuación se adjuntan dos ejemplos para ver un poco de skinning. Los dos ejemplos sirven para obtener el mismo resultado visual. Veamos primero con Group:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
    3.     xmlns:s="library://ns.adobe.com/flex/spark"
    4.     xmlns:mx="library://ns.adobe.com/flex/halo"
    5.     >
    6.     <s:Group>
    7.         <s:Rect width="100%" height="100%"
    8.             x="0" y="0" radiusX="4"  radiusY="4">
    9.             <s:stroke>
    10.                 <s:LinearGradientStroke weight="1"  scaleMode="normal"/>
    11.             </s:stroke>
    12.             <s:fill>
    13.                 <s:LinearGradient>
    14.                     <s:entries>
    15.                         <mx:GradientEntry color="0x999999"/>
    16.                     </s:entries>
    17.                 </s:LinearGradient>
    18.             </s:fill>
    19.         </s:Rect>
    20.         <s:Button label="Button 1" left="10" top="13" bottom="10"/>
    21.         <s:Button label="Button 2" left="110" top="13" bottom="10"/>
    22.         <s:Button label="Button 3" left="210" top="13" bottom="10"/>
    23.         <s:Button label="Button 4" left="310" right="10" top="13" bottom="10"/>
    24.     </s:Group>
    25. </s:Application>

    Para obtener el mismo resultado usando un SkinnableContainer y una skin, tenemos el siguiente código; la skin:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
    3.     xmlns:mx="library://ns.adobe.com/flex/halo"
    4.     xmlns:s="library://ns.adobe.com/flex/spark"
    5.     alpha.disabled="0.5">
    6.     <fx:Metadata>
    7.         [HostComponent("spark.components.SkinnableContainer")]
    8.     </fx:Metadata>
    9.  
    10.     <s:states>
    11.         <s:State name="normal"/>
    12.         <s:State name="disabled"/>
    13.     </s:states>
    14.    
    15.     <s:Rect
    16.         width="100%" height="100%"
    17.         x="0" y="0" radiusX="4"  radiusY="4">
    18.         <s:stroke>
    19.             <s:LinearGradientStroke weight="1"/>
    20.         </s:stroke>
    21.         <s:fill>
    22.             <s:LinearGradient>
    23.                 <s:entries>
    24.                     <mx:GradientEntry color="0x999999"/>
    25.                 </s:entries>
    26.             </s:LinearGradient>
    27.         </s:fill>
    28.     </s:Rect>
    29.    
    30.     <s:Group id="contentGroup" left="5" right="5" top="5" bottom="5">
    31.         <s:layout>
    32.             <s:VerticalLayout />
    33.         </s:layout>
    34.     </s:Group>
    35. </s:Skin>

    El container:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2.  <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:s="library://ns.adobe.com/flex/spark">
    3.    <s:SkinnableContainer skinClass="mySkins.MyBorderSkin">
    4.      <s:layout><s:HorizontalLayout gap="10"/></s:layout>
    5.      <s:Button label="Button 1"/>
    6.      <s:Button label="Button 2"/>
    7.      <s:Button label="Button 3"/>
    8.      <s:Button label="Button 4"/>
    9.    </s:SkinnableContainer>
    10. </s:Application>

    Tanto si aplicamos el primer ejemplo, como este último, el resultado es:
    im21.png

    Panel
    Incluye la barra de título, el título, un border y la content area para albergar a los hijos.Su layout por defecto es BasicLayout.

    DataGroup y SkinnableDataContainer

    Aunque DataGroup y SkinnableDataContainer pueden contener cualquier componente que implemente la interface IVisualElement y sea subclase de DisplayObject, están pensados para contener data items. Para representar estos data items debemos usar item renderers. Los item renderers convierten los datos a un formato visual. La mayor diferencia entre estos containers es que SkinnableDataContainer puede ser skineado.

    No definen por defecto ningún item renderer para renderizar los items, por eso, si no lo especificamos, no se muestra nada. Fx4 proporciona dos item renderers por defecto:

    • DefaultItemRenderer, que convierte los datos a un simple String.
    • DefaultComplexItemRenderer, que muestra cada uno de los datos en un container de tipo Group. Es adecuado cuando entre los hijos hay algún elemento visual.

    A partir de cualquier item renderer podemos acceder al host component mediante la propiedad owner.

    Custom item renderers

    Para codificar un item renderer, debemos definir un nuevo componente mxml, con ItemRenderer como tag root.

    Paso de datos al item renderer
    La clase base de todo item renderer es ItemRenderer y define tres propiedades que el owner usa para pasarle la información:

    • labelText: Cuando los datos recibidos son de tipo String.
    • data: Cuando los datos tienen una representación más compleja.
    • owner: Es el componente que hospeda el item renderer. El owner de un item renderer debe implementar la interface IItemRendererOwner, que define las siguientes funciones para el item renderer: itemToLabel(), para convertir el data item en una representación en forma de String; y updateRenderer(), que codifica el data item como un String para la propiedad labelText del item renderer; luego actualiza la referencia de la propiedad owner del item renderer.

    Debemos tener en cuenta que los item renderers deben definir los siguientes estados:

    • normal: cuando no se está interactuando con él.
    • hovered: cuando el mouse está encima.
    • selected: cuando está seleccionado.

    Podemos definir estados adicionales según lo que necesitemos. A continuación se muestra un ejemplo de item renderer:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
    3.     xmlns:s="library://ns.adobe.com/flex/spark"
    4.     xmlns:mx="library://ns.adobe.com/flex/halo">
    5.     <s:DataGroup itemRenderer="MyItemRenderer">
    6.         <s:layout><s:VerticalLayout /></s:layout>
    7.         <mx:ArrayCollection>
    8.             <fx:Object firstName="Sergi" lastName="Dote" companyID="11233" phone="65656565"/>
    9.             <fx:Object firstName="Joan" lastName="Garnet" companyID="13455"phone="650321232"/>
    10.             <fx:Object firstName="Alberto" lastName="Albericio" companyID="11543" phone="617555214"/>
    11.         </mx:ArrayCollection>
    12.     </s:DataGroup>
    13. </s:Application>

    El item renderer:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2. <s:ItemRenderer
    3.     xmlns:fx="http://ns.adobe.com/mxml/2009"
    4.     xmlns:mx="library://ns.adobe.com/flex/halo"
    5.     xmlns:s="library://ns.adobe.com/flex/spark"
    6.     >
    7.     <s:states>
    8.         <s:State name="normal"/>
    9.         <s:State name="hovered"/>
    10.         <s:State name="selected"/>
    11.     </s:states>
    12.     <s:Rect
    13.         left="0" right="0" top="0" bottom="0">
    14.         <s:fill>
    15.             <s:SolidColor color="0xFFFFFF"/>
    16.         </s:fill>
    17.         <s:fill.hovered>
    18.             <s:SolidColor color="0xCEDBEF"/>
    19.         </s:fill.hovered>
    20.         <s:fill.selected>
    21.             <s:SolidColor color="0xA8C6EE"/>
    22.         </s:fill.selected>
    23.     </s:Rect>
    24.     <s:HGroup
    25.         horizontalCenter="0" verticalCenter="0"
    26.         >
    27.         <s:SimpleText text="{data.lastName}, {data.firstName}"
    28.             color.hovered="blue" fontWeight.hovered="bold"  />
    29.         <s:SimpleText text="{data.companyID}" color.hovered="blue"
    30.             fontWeight.hovered="bold"/>
    31.         <s:SimpleText text="{data.phone}"
    32.             color.hovered="blue"
    33.             fontWeight.hovered="bold"
    34.             />
    35.     </s:HGroup>
    36. </s:ItemRenderer>

    El resultado sería algo así:
    im31.png
    Igual que en Halo, podemos definir una function como item renderer, mediante la propiedad itemRendererFunction.

    También se mantiene la posibilidad de declarar inline item renderers, definiéndolo dentro de fx:Component. La única restricción es que no podemos dejar el tag fx:Component vacío. Cuando trabajamos con fx:Component, podemos acceder al ámbito exterior del item renderer mediante la keyword outerDocument. Los inline item renderers también pueden usarse en cualquier lugar de la aplicación, debemos especificar un id para el tag fx:Component, así podemos hacer referencia a él.

    Precedencia de los Spark item renderers

    Tanto DataGroup como SkinnableDataContainer siguen reglas para determinar el item renderer que aplicarán:

    1. Si la propiedad itemRendererFunction está definida, se usa para obtener el renderer. Si ésta devuelve null, aplica la regla 2
    2. Si el itemRenderer está definido, se usa para renderizar los datos
    3. Si el item implementa mx.core.IVisualElement y es de tipo flash.display.DisplayObject, se usa directamente
    4. Si no se ha encontrado ningún item renderer se lanza un error en tiempo de ejecución

    Uso de la virtualization

    DataGroup y SkinnableDataContainer pueden representar infinidad de items. Cada item require una instancia de item renderer. Es fácil pensar la degradación de rendimiento que nuestra apliación puede sufrir en función del número de items a representar.

    En lugar de crear un item renderer para cada item a representar, podemos decirle al contenedor que tenga un layout virtual: de esta forma el container reusa los item renderers y solo crea los que necesita para la parte visible. Para configurar el container para que use use virtual layout, settearemos la propiedad useVirtualLayout a true. Sólo DataGroup y SkinnableDataContainer con VerticalLayout, HorizontalLayout y TileLayout soportan virtual layout. Con layout virtual se disminuye la carga de memoria y renderizado.

    Hay diferencias en el comportamiento de los layouts cuando se activa el virtual layout o no:

    • Con virtual layout true no hay soporte para percentHeight = 100% en un VerticalLayout y tampoco para percentWidth = 100% para un HorizontalLayout.
    • Un container que use virtual layout y contenga pocos items a representar, cuyas dimensiones varien bastante, puede responder de manera pobre a la interacción del scrolling. Usar virtual layout con pocos elementos es evitable ya que es mayor el cálculo y la creación o measure.

    Debido a que con virtual layout un container reusa los item renderers, tenemos que estar seguros que definimos todos sus estados.

    Uso del typical item

    Cuando usamos virtual layout en un DataGroup o un SkinnableDataContainer, podemos pasar al contendor un data item que define el data item típico. El container usará este data item típico y lo asocia al item renderer para determinar las dimensiones por defecto de los hijos. Esto se hace con la propiedad typicalItem del contenedor, y debemos especificar el data item como en este ejemplo:

    MXML:
    1. <?xml version="1.0" encoding="utf-8"?>
    2.   <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:s="library://ns.adobe.com/flex/spark">
    3.  <s:layout><s:VerticalLayout/></s:layout>
    4.  <fx:Script>
    5.    <![CDATA[
    6.     [Bindable]
    7.     public var typicalObj:Object = {firstName:"Long first name", lastName:"Even longer last name", companyID:"123456", manager:"yes"};
    8. ]]>
    9.  </fx:Script>
    10.  <s:Scroller>
    11.  <s:DataGroup itemRenderer="myComponents.MySimpleItemRenderer" height="100" typicalItem="{typicalObj}">
    12.   <s:layout><s:VerticalLayout useVirtualLayout="true"/></s:layout>
    13.   <mx:ArrayCollection>
    14.    <fx:Object firstName="Bill" lastName="Smith" companyID="11233" manager="yes"/>
    15.    <fx:Object firstName="Dave" lastName="Jones" companyID="13455" manager="no"/>
    16.    <fx:Object firstName="Mary" lastName="Davis" companyID="11543" manager="yes"/>
    17.    <fx:Object firstName="Debbie" lastName="Cooper" companyID="14266" manager="no"/>
    18.   </mx:ArrayCollection>
    19.  </s:DataGroup>
    20.  </s:Scroller>
    21. </s:Application>

    En el ejemplo, con la definición de typicalObj representamos la propiedad typicalItem del container. El container usará este data item y asociado con el item renderer, determinará las dimensiones de su children.

    Con este artículo hemos dado un repaso a los containers spark, entrando en profundidad en aquellos aspectos que he creido más importantes. Espero que os sea de ayuda para entender más esta nueva arquitectura de los containers.

    Comparte:



    4votos  Vota!!

    Acerca de esta entrada