Fx4 VI: Spark Layouts

La arquitectura Spark ha querido mejorar los Layouts para ganar más flexibilidad en la disposición de los elementos. El objetivo más importante ha sido el de separar el layout de las características individuales de cada Container, a diferencia de Halo. Los Containers en Flex 4 no controlan su layout, son los layouts que controlan como albergar a sus hijos. De esta manera conseguimos que los containers tengan mucha más versatilidad; un mismo contenedor puede contener de diferente manera simplemente cambiando su layout.

Comparación entre los layouts Spark vs Halo

Los layouts Spark se basan en la potencia de los layout Halo. Comparemos ambas arquitecturas.

Características comunes

Propiedades como width, height, minWidth, explicitWidth y percentWidth, la sintaxis y semántica no han cambiado. Características de estilo como left, right, top, horizontalCenter o baseline , entre otras, siguen siendo soportadas.
El núcleo de la arquitectura de layouts, la clase LayoutManager y el ciclo de vida de los componentes en este ámbito, también han sido mantenido. Los tres métodos conocidos por todos, commitProperties(), measure() y updateDisplayList(), siguen siendo llamados por el LayoutManager en el mismo orden y las reglas de invalidación siguen siendo las mismas: los componentes siguen teniendo su default size calculada a partir de measure() y estos siguen configurándose a partir de las dimensiones de los hijos, llamando al método updateDisplayList().

Mejoras y nuevas características

Seguramente el cambio que más sorprende es que en Spark, los layouts hayan sido separados de los containers a los que están relacionados. Cuando un container Spark llama a measure() o a updateDisplayList(), estas tareas se delegan directamente a la instancia layout Spark asociada. Comentemos que mejoras nos proporciona esta separación:

  • La lógica de los layouts se ha implementado en clases separadas que heredan de LayoutBase. LayoutBase es el mínimo necesario para que una clase sea un Spark layout.
  • La nueva API implica un contrato entre los Spark layouts y sus hijos: éstos elementos son representados por la interface ILayoutElement y los layouts soportan transformaciones 2D y 3D. Este soporte está construido en la interface ILayoutElement que es implementada por todos los hijos del Spark container asociado. De esta manera se pueden desarrollar layouts a medida.
  • Layout virtualization: Los Spark layouts soportan la virtualización de los ítem renderers de manera sencilla.
  • El orden de renderización de la display list está desacoplada del orden de declaración de sus hijos, de manera que, por ejemplo, el hijo #1 puede ser dibujado en último lugar.
  • Layouts asignables: debido a que los containers ya no están acoplados al contenedor, podemos intercambiar los layouts incluso en tiempo de ejecución. Así conseguimos reducir el número de containers, conseguimos más modularidad y mejoramos la reutilización de código. Un ejemplo: en Halo teníamos una sola List, en Spark podemos tener tantas List como layouts tengamos disponibles.
  • Custom layouts: Gracias a la separación entre layout y container, la nueva clase LayoutBase y la interface ILayoutElement, es muy fácil crear potentes layouts.
  • Scrolling pixel a pixel: La nueva virtualización soportada por DataGroup permite el scroling a nivel de pixel.
  • Profundidad: Podemos especificar la profundidad de cada hijo para saber quien se muestra delante de quién.
  • Transformaciones Post-layout: Podemos especificar x, y, z, rotation, scale y mucho más sin afectar al layout.
  • Sistema de coordenadas consistente: Todas las propiedades relacionadas con las dimensiones, como width, height, measuredWidth y measuredHeight están siempre aplicacadas consistentemente.

Los distintos layouts

A continuación describiremos de manera fácil los distintos layouts que nos proporciona Flex 4.

BasicLayout

Nos proporciona un layout de tipo absoluto. Trabaja de manera similar a un Canvas de Halo. Soporta constraints (left, right, top, bottom, horizontalCenter y verticalCenter), tamaño por porcentage (percentWidth y percentHeight) y soporta objetos con transformaciones 2D (rotation, scale y skew).

Scrolling

BasicLayout no sobreescribe el comportamiento de su superclase LayoutBase en cuanto a scrolling. Su ScrollBar hace incrementos pixel a pixel.

TileLayout

Se comporta como un container Tile de Halo: distribuye los elementos en columnas y filas de tamaños concretos.

Diferencias con el container Tile de Halo

  • Nuevas propiedades de justificación: justifyRows, justifyColumns
  • Nuevas propiedades columnCount, rowCount
  • No soporta estilos: horizontalAlign, verticalAlign, padding, horizontalGap, verticalGap
  • TileLayout limita las dimensiones de los elementos cuando éstas se especifican mendiante porcentage, a diferencia del container Halo.
  • Lógica de scrolling.

HorizontalLayout

Este layout nos aplica una distribución horizontal como si se tratara de un container HBox.

VerticalLayout

Se comporta tal que un container VBox.

Tanto HorizontalLayout como VerticalLayout no soportan constraints, pero si permiten tamaño por porcentage y tienen propiedades como gap y align. Permiten scroll a través de su eje principal.

Uso de los Spark Layouts

Debido a la separación entre layouts y containers en Spark, hay importantes cambios que debemos saber. La siguiente lista nos muestra la equivalencia entre Halo y spark en cuanto a containers y sus layouts:
• Canvas - Group con BasicLayout
• HBox – Group con HorizontalLayout o HGroup
• VBox – Group con VerticalLayout o VGroup
• Tile – Group con TileLayout
• List – List con VerticalLayout
• TileList – List con TileLayout

Sintaxis MXML

Para especificar un layout a un container, debemos dar valor a su propiedad layout. A continuación vemos un ejemplo de cómo especificar un layout a un container y las propiedades gap y verticalAlign:

En el ejemplo siguiente, se disponen unos elementos mediante un layout horizontal y otro vertical:

El resultado del código anterior es:

Scrollbars

Los Spark containers Group y DataGroup no tienen suporte directo para scroll como hacía Halo. Spark tiene unas propiedades para configurar un scroll:clipAndEnableScrolling, horizontalScrollPosition, verticalScrollPosition, contentWidth y contentHeight. Para usarlo, encapsulando Group o DataGroup con el tag Scroller haremos que se vean scrollbars cuando sean necesarias:

Los layouts spark permiten per-pixel scrolling. Para más información mirar la API Description.

Los Spark layouts a bajo nivel

En el diseño de los Spark layouts se ha querido mejorar el tratamiento de los objetos, incluso si estos tienen transformaciones complejas. También se ha querido facilitar la creación y extensión de layouts.

interface ILayoutElement

Representa los distintos objetos que van a ser distribuidos por el layout. Cualquier objeto que quiera ser albergado por un layout Spark, debe implementar la interface ILayoutElement.
Podemos aplicar transformaciones a los ILayoutElements usando una matriz de transformación.
La interface ILayoutElement contiene la infomación sobre las preferencias del layout:

  • Fronteras ( preferred bounds ) del componente, que determinan el tamaño ideal en caso de que no hayan restricciones. En caso contrario la recursividad del layout haría que se entrara en un bucle infinito.
  • Porcentages de tamaños de los componentes contenidos respecto al contenedor (opcional).
  • Constraints: left, top, right, bottom, horizontalCenter, verticalCenter, baseline.

Además, ILayoutElement nos proporciona métodos para manipular la distribución de cada hijo y el tamaño.

Spark Virtualization

la virtualización es una optimización referente al rendering de los layouts, en cuanto al container y al número de ítems que alberga. La virtualización es soportada por el container DataGroup con VerticalLayout, HorizontalLayout y TileLayout.
La virtualización optimiza la creación y el rendering de los items de manera proporcional al número de items visibles en el container.
Una de las cosas que se tienen que tener en cuenta con el uso de la virtualización es que si tenemos un layout con un número pequeño de items, puede ser que responda con lentitud debido al proceso de rendering. En este caso seguramente sea apropiado no usar la virtualización. Es indicada para aquellos casos en que la creación y el cálculo del método measure de los items del layout sean complejos debido a un gran número de items.

Para usar la virtualización, se debe settear la propiedad useVirtualLayout del layout a true. el proceso de measure de un DataGroup con virtualización se basa en las dimensiones del primer item renderer o en las dimensiones del typical item, definido mediante la propiedad typicalItem.

Nueva clase base de los layout

La clase base de los layouts LayoutBase tiene los métodos elementAdded() y elementRemoved() y las propiedades useVirtualLayout, que como hemos dicho indica que se desea aplicar la virtualización, y typicalLayoutElement, que nos permite definir la geometría típica del layout.

¿Qué implica el reciclaje de los item renderers virtuales?

Tal y como hemos dicho, el container DataGroup recicla los item renderers cuando su propiedad useVirtualLayout tiene valor true y la itemRendererFunction no se ha especificado. Cuando un item renderer cae fuera de la vista debido a la acción del scroll, su propiedad data se settea a null, de manera que se libera. Más tarde, cuando el item renderer es necesario, se le asigna la propiedad data antes de que el layout lo represente. El reciclaje mejora el rendimiento del scrolling evitando el coste de creación de item renderers.


Redimensionamiento y posicionamiento

Flex establece una serie de reglas para estas funciones: una combinación de reglas de redimensionado para los componentes y reglas de redimensionado y posicionamiento para los containers.

Debido a que Flex define reglas por defecto, muchas veces no se debe hacer nada. Cada container tiene propiedades y estilos para configurar el aspecto del layout.

LayoutManager

La clase LayoutManager controla el layout en Flex. Es interesante conocer los pasos que sigue este manager, en cuanto a la determinación de las dimensiones y el posicionamiento de cada componente:

1-Commitment pass

Determina las configuraciones de los componentes. Esta fase permite a los componentes que se reconfiguren antes de que Flex determine sus dimensiones y posiciones. Esto se hace obligando a cada componente a llamar su función commitProperties() durante esta etapa.

2-Measurement pass

Se calcula la dimensión por defecto de cada componente. Este paso empieza por los componentes más internos y acaba con el propio container.

Durante esta etapa se llama a la función measure() de cada componente para que éste calcule sus dimensiones por defecto. Todas la propiedades del dimensionado se calculan antes de que Flex aplique cualquier transformación al componente, como rotaciones.

3-Layout pass

Hace las acciones referentes al Layout, como posicionamiento y redimensión. Esta fase empieza por el container más externo y va hasta el componente más interno. En esta fase se determinan las dimensiones actuales y la posición de cada componente. En este paso también se realizan las funciones de dibujado, si es necesario, como por ejemplo lineTo() o drawRect().
Layout Manager provoca que los componentes llamen a updateDisplayList(), por eso este paso también se conoce como el update pass.


¿Cuándo se ejecuta el layout?

Flex ejecuta el layout de sus componentes cuando el container se inicializa. Luego, también se ejecuta cuando el usuario hace alguna cosa que afecte las dimensiones o posicionamiento de los elementos visuales, acciones que provoquen la modificación de propiedades como x, y, width, height, scaleX, scaleY, rotationX, rotationY o transformaciones de la matriz.


Pasos importantes en la creación de un custom Spark Layout

Vamos a describir los pasos más importantes a la hora de crear un custom Spark Layout.

Determinar dimensiones y posiciones de los elementos

Los requerimientos mínimos de cualquier Spark layout es que se herede de LayoutBase y se implemente el método updateDisplayList(). Dentro de este método se posicionarán los elementos y les daremos las dimensiones adecuadas.

Cuando un layout es asignado a un container, la propiedad target del layout se actualiza con este container, para que se pueda acceder al child del container. Containers especiales como DataGroup y List usan instancias de item renderers para cada elemento que visualizan. De la creación, reciclaje y destrucción de estos item renderers se encarga el container.
Para acceder a los elementos del layout lo haremos de las siguientes maneras:

Hagamos una comparación entre las APIs de Halo y las de Spark referente al uso de los layouts:

Añadir soporte para Scrolling

Se deben seguir estos pasos:
1. Añadir ScrollBars.
2. Conectar el scrollbar y propiedades horizontalScrollPosition y verticalScrollPosition del layout.
3. Calcular los valores del scrollbar y mantener sincronía con los cambios.

Calcular las dimensiones por defecto del container

En Flex, el proceso conocido como “measurement”, determina el tamaño por defecto de un componente. Estas dimensiones por defecto se usan cuando el componente no tiente un tamaño definido explicitamente; por ejemplo, un Canvas Halo que contiene una Label, y que no tiene especificados ni width ni height, usa el proceso de “measure” para calcular el área que necesita para mostrar esa Label.

En Spark, cuando un container necesita ejecutar el proceso de “measure”, se ejecuta el método measure() del layout asociado. En este proceso, básicamente lo que hace el layout es iterar por los distintos elementos y calcular el área ideal para poder representarlos. Luego el layout settea las dimensiones por defecto al container, mediante las propiedades measuredWidth, measuredHeight, measuredMinWidth y measuredMinHeight. El container se redimensiona y finalmente, el layout ejecuta updateDisplayList().

Aunque este es el proceso normal, hay algunas cosas que debemos tener en cuenta:

  • El método measure() no se llama siempre; por ejemplo, si un container tiene definidas las dimensiones explícitamente, el proceso de “measurement” no se ejecuta.
  • Los containers que tienen activados la opción de clipping y scrolling ignoran las dimensiones mínimas calculadas, debido a que el usuario puede scrollear y ver cualquier parte del contenido del container.
  • Para entender el proceso de “measurement”, es una muy buena práctica testear este proceso en layouts y containers que nos proporciona Spark, por ejemplo Group.

    A tener en cuenta…

    Os comento algunos aspectos a tener en cuenta y que he aprendido al hacer este artículo.

    Acceso correcto a los elementos del layout

    Debido a que podemos usar layout virtual, para no obtener nulls, la manera correcta de acceder a los elementos es la siguiente:

    en lugar de:

    Control sobre el layout

    UIComponent implementa la interface ILayoutElement que proporciona funcionalidades para controlar los elementos del layout. Para los layouts 2D se debe usar element.setLayoutBoundsSize( width, height, postLayoutTransform ) para determinar las dimentsiones y element.setLayoutBoundsPosition( x, y, postLayoutTransform ) para determinar la posición.

    transformaciones 3D

    Podemos usar element.setLayoutMatrix3D( matrix, postLayoutTransform ) para determinar la matriz 3D a aplicar al elemento. El orden que se suele seguir para aplicar una transformación 3D es el siguiente:

    1- Aplicar las coordenadas 3D al element.
    2- Aplicar la rotación.
    3- Centrar el item al container.

    Ejemplo:

    maintainProjectionCenter

    Flash Player 10 facilita una nueva propiedad llamada perspectiveProjection, que trabaja de manera similar a una cámara en librerías como Away3D o PV3D. Por defecto, esta propiedad recibe el valor top-left, pero a menudo necesitaremos que se situe en el centro del container. Para hacerlo:

    layer

    Una cosa a tener en cuenta de FP10 es que la propiedad z no tiene relación con el z-index (índice de profundidad en la coordenada z) del DisplayObject. Esto significa que aunque un objeto DisplayObject puede parecer estar lejos por estar representado más pequeño, se mantendrá delante de todo si no se actualiza su posición en la display list, lo que hará que se coloque delante de otros elementos y dará una mala sensación de profundidad.

    Para solucionar esto con Flex 4, debemos trabajar con la propiedad layer de la clase UIComponent. Simplemente debemos determinar la propiedad layer para cada elemento, basado en su propiedad z ideal, y si se require, llamar a container.invalidateLayering() para validar correctamente la layer modificada.

    Ejemplos de layouts custom

    Mostraremos un par de ejemplos de layouts custom que se han creado aprovechando el conocimiento adquirido y también modificando cosas que se han encontrado por la red.

    El primer ejemplo muestra un layout que posiciona sus elementos según las agujas del reloj.

    Primero mostramos el código principal de la aplicación: Tenemos un container de tipo DataGroup, que recibe un data provider y que contiene una serie de elementos albergados en un item renderer que hemos definido a nuestro antojo:

    Al container DataGroup le asignamos un layout de tipo CircularLayout, que se ha definido extendiendo la clase LayoutBase, y el código de la cual se muestra a continuación:

    El item renderer es el siguiente:

    El resultado es el siguiente (es un thumbnail debido a que la imagen original es bastabte grande):
    circular.png

    A continuación os adjunto un proyecto para que veais otro ejemplo que utiliza este layout circular, pero con más potencia. Los elementos se disponen en círculo y en el centro de éste, se muestra con mayor tamaño el elemento seleccionado. Podríamos usar este ejemplo para crear una galería de imágenes, por ejemplo.

    Os adjunto el proyecto y una captura del ejemplo. No comento el código, ya que parte del ejemplo anterior y es fácil entender el código.

    proyecto comprimido: layoutarchitecture.zip

    Captura de pantalla:

    Enlaces

    A continuación, para completar este artículo, os adjunto algunos links de bloggers que han escrito artículos interesantes sobre layouts, de los cuales he aprendido y que creo que debo compartir con vosotros:

    Varios ejemplos de layouts
    Layout concéntrico
    Random layout
    Artículo sobre layouts
    Wheel layout

    Conclusión

    Los layouts son uno de los aspectos más interesantes y extensos de Flex 4. Hemos dado un paseo por los puntos que he creido más interesantes.
    Por otra parte, este ha sido un artículo extenso, pero espero que haya servido para tener una idea de lo que significan los layouts en Flex 4.

    Comparte:



    5votos  Vota!!

Acerca de esta entrada