MadeInFlex
Creación de componentes gráficos ligeros con Spark
Octubre 23rd, 2010 - [Enlace local]
Al crear componentes en Flex, siempre deberíamos tener en mente la optimización: un componente más ligero ocupará menos memoria y repercutirá positivamente en la respuesta de la aplicación. Es un punto importantísimo, aunque, a menudo, no lo tengamos en cuenta, posiblemente debido a que cada vez tenemos equipos más potentes.
Es necesario si queremos tender a una programación que no exija un consumo de memoria excesivo y que no perjudique la performance de la aplicación, y es imprescindible para desarrollar aplicaciones con Flex para mobile devices.
Distintos tutoriales de Flex 4 explican que para crear componentes custom, extendamos SkinnableComponent, le demos el comportamiento deseado, tratemos los eventos y estados del componente y que finalmente creemos la skin para tener la representación visual del componente.
Después de leer varios artículos he descubierto unas clases que pueden ayudar a la creación de componentes gráficos custom y que quisiera compartir con vosotros.
SpriteVisualElement
Una de las clases de las que quiero hablar en este post es SpriteVisualElement.
Comparación entre SpriteVisualElement y UIComponent
Comparando esta clase con UIComponent, que es la clase base que se suele usar para componentes visuales, tanto los interactivos, como los que no, vemos que SpriteVisualElement parece ser más light-weight, ya no sólo pro el número de líneas que contiene esta clase (2900 aprox. UIComponent tiene unas 13000), sino por el número de interfaces que deben implementar cada una (SpriteVisualElement implementa 3 interfaces, UIComponent implementa 16).
UIComponent aporta más características, pero si no las necesitamos, SpriteVisualElement es la clase base ideal.
En la página de SpriteVisualElement dentro de la Language Reference, se describe como la clase más ligera para las implementaciones basadas en Sprite de la interface IVisualElement y que los layouts Spark pueden gestionar objetos de este tipo.
Si usamos ActionScript para añadir un componente FXG a nuestra aplicación, éste debe ser de tipo SpriteVisualElement. IVisualElement proporciona las constraints de layout tal que height, width, top, right, bottom, left…
Otro punto importante es que al ser una clase hija de Sprite, tiene las características de cualquier Flash interactive object, es un Sprite que puede ser usado en arquitecturas Spark.
Creación de un Custom SpriteVisualElement component
El proyecto que adjunto viene a ser como un cronómetro: haciendo click sobre él, podemos ponerlo en marcha o pararlo.
Pasos en la vida del componente
Al crear custom components debemos tener muy en cuenta el ciclo de vida del componente, y en este caso las dimensiones y layout del componente, importante a la hora de redibujarlo. Durante el proceso de layout del componente, Spark hace una llamada a setLayoutBoundsSize(width, height, postLayoutTransform), que determina las dimensiones del componente en la disposición de éste, es decir, las dimensiones que usa para dibujarse.
La llamada a setLayoutBoundsSize puede afectar a la posición del layout. Podemos llamar setLayoutBoundPosition() después de haber llamado a setLayoutBoundSize() para reubicar el componente.
En el código de ejemplo la función setLayoutBoundsSize ha sido sobrescrita:
Cuando el componente cambie una propiedad que afecte a su distribución y repintado, debemos invalidarlo para que Flex lo redibuje. Lo haremos con invalidateParentSizeAndDisplayList().
Al producirse esta invalidación, Flex lanza la función setLayoutBoundsSize para repintar el componente con los nuevos cambios en las propiedades.
GraphicElement
Es una clase más ligera que SpriteVisualElement, poniéndose, en cuanto a jerarquía, al nivel de DisplayObject. Es la clase base para definir un elemento gráfico, como shapes, text o images. Al definir un objeto gráfico, debemos especificar las dimensiones del elemento, pero no nos permite usar una especificación de éstas en valores porcentuales.
En este ejemplo,el autor nos muestra un uso adecuado de esta clase.
Debemos tener claros dos conceptos: las fronteras que delimita un determinado objeto dentro del espacio de coordenadas del padre y las fronteras que delimita dentro de su propio espacio de coordenadas.
Para hacer transformaciones necesitamos la clase Matrix, lo que sobrescribe valores de transformación previamente definidos como rotation, scaleX, scaleY, x e y. Si setteamos transform.matrix o las propiedades de transformación en ActionScript, se usarán los últimos valores aplicados.
La responsabilidad de la clase GraphicElement y sus subclases es la de dibujar componentes gráficos Spark, componentes FXG que parten de GraphicElement.
No confundir GraphicElement con DisplayObject, ya que la primera no nos da ningún tipo de interacción. GraphicElement usa un DisplayObject interno para dibujarse. Dentro del mismo display object podemos dibujar varios elementos gráficos. Así reducimos sobrecarga y damos un mayor rendimiento.
Será necesario implementar ciertas interfaces como IVisualElement y IGraphicElement y así poder ser dibujada y dispuesta por Spark.
Puntos claves de GraphicElement:
- GraphicElement usa un DisplayObject, que puede ser compartido, para dibujar dentro de él.
- Cuando un GraphicElement necesita ser repintado, todos los elementos gráficos que comparten el mismo DisplayObject se redibujan.
- Al crear un GraphicElement que comparte un display object con otros elementos gráficos, debemos asegurarnos de no hacer un clear de la instancia graphics, esto eliminaría las anteriores representaciones hechas por otros elementos.
- Si aplicamos ciertas propiedades al DisplayObject, éstas serán aplicadas a todos los elementos gráficos que compartan este display object. Esto implica que el uso de filtros, transformaciones, etc., a una determinada instancia, requieren que el GraphicElement tenga su display object propio. Con la implementación estándar lo solucionamos.
- GraphicElement necesita acceder a una instancia de graphics para dibujar. Podemos extraer esta referencia de la propiedad drawnDisplayObject de GraphicElement.
- Al dibujar, debemos tener en cuenta el desplazamiento del elemento gráfico en relación a su objeto de visualización.
Creación de un custom GraphicElement
Con FXG y el elemento Path, dibujamos la mayoría de elementos gráficos. Un Path se representa en un display object. A veces nos será más fácil crear elementos gráficos con GraphicElement, sobre todo cuando el elemento a dibujar es dinámico en cuando a cambio en su forma.
Al crear un GraphicElement custom debemos considerar lo siguiente:
- Sobrescribir la function updateDisplayList() para refrescar el dibujado del elemento.
- Usar una instancia de graphics para dibujar: (drawnDisplayObject as Sprite).graphics.
- Respetar las coordenadas del objeto GraphicElement en relación de su DisplayObject, mediante las propiedades drawX y drawY de la clase GraphicElement.
- La función que dibuje el elemento no puede hacer un clear de graphics, ya que el DisplayObject asociado puede ser compartido y esto provoca el borrado de los gráficos de otros elementos.
- GraphicElement implementa la interface IInvalidating para tomar parte en el proceso de invalidación. Si una propiedad que afecta al repintado del elemento es setteada, debemos invalidar este elemento y actualizarlo en el paso de validación.
Control del display object compartido
El control de los display objects asociados a un GraphicElement lo hace Flex. Si necesitamos manipular personalmente el DisplayObject en lugar de dejárselo a Flex, se nos ofrece diferentes vías:
- needsDisplayObject() nos permite determinar si el GraphicElement necesita su propio display object. La implementación por defecto comprueba si hay propiedades que requieren un display object propio (rotation, color transformation, blend modes, etc.).
- canShareWithNext() dice si el display object puede compartirse con el siguiente IGraphicElement de la secuencia.
- canShareWithPrevious() nos dice si el display object puede compartirse con el anterior IGraphicElement de la secuencia.
- Cuidado con el uso de displaySharingMode. No funciona como se espera, ya que Flex la usa para administrar los objetos. Si la usamos, los valores se sobrescribirán manualmente.
- Si Flex necesita crear un display object para un determinado GraphicElement, invoca la función itscreateDisplayObject(). La implementación por defecto nos devuelve un objeto de tipo InvalidatingSprite que implementa la interface ISharedDisplayObject.
- Subreescribir la function createDisplayObject()
- Settear mouseEnbled y mouseChildren del DisplayObject creado a true
- Añadir event listeners al DisplayObject
- Sobreescribir needsDisplayObject() y devolver true
- Sobreescribir canShareWithNext() y canShareWithPrevious() y devolver false
Interacción
GraphicElement tiene como finalidad dibujar gráficos de manera optimizada, compartiendo display objects. No está pensado como elemento de interacción, el elemento se incrusta en el container Spark y la interacción debe hacerse a través del container.
Para permitir que un GraphicElement tenga interacción directa, debemos llevar a cabo ciertos aspectos que en muchos casos se consideran malas prácticas. Vamos a ver como conseguirlo.
El DisplayObject que usa el GraphicElement para dibujarse, es un InvalidatingSprite, que tiene las propiedades mouseEnabled y mouseChildren a false. Esto hace que la interactividad esté desactivada.
Debemos habilitar la interactividad en el element InvalidatingSprite y deshabilitar que el display object sea compartido para evitar que el repintado de otros elementos gráficos pueda afectar a este. Para ello es necesario:
Conclusiones
SpriteVisualElement es una clase a tener en cuenta cuando queremos crear componentes gráficos ligeros, ya que los containers Spark aceptan elementos de este tipo y se pueden usar dentro de skins o combinarlos con FXG.
Por otro lado, la clase GraphicElement nos permite crear elementos gráficos muy optimizados, pero, como hemos visto, tiene la interactividad limitada.
Si necesitamos interacción directa con un elemento gráfico, es mejor usar SpriteVisualElement, que es un objeto interactivo (extiende de InteractiveObject).