Mobile Flex Apps are around the corner. Hence it might be a good thing to inspect some of the more light-weighted approaches of Flex to create visual elements and components that can take part in the Spark layout process. In this last example I had a look at SpriteVisualElement.
Now, it’s time for the GraphicElement.
Example using custom Graphic Elements. View source is enabled.
The GraphicElement and its subclasses are responsible to draw a significant amount of the Spark components graphics. FXG elements like Rect, Path, Line etc. all derive from GraphicElement.
A GraphicElement is actually neither a DisplayObject nor does it offer any form of interaction. Its super class is the EventDispatcher and it uses an internal DisplayObject to draw itself into. It does implement interfaces like IVisualElement and IGraphicElement and can therefore be laid out and displayed by Spark.
The internal display object used for drawing purposes is set and managed by Flex and multiple graphic elements can draw onto the same display object. Sharing display objects reduces overhead and in most cases leads to better performance.
This naturally has some implications which need to be considered when creating a custom GraphicElement
- A GraphicElement gets a DisplayObject to draw into and the display object might be shared. Both is managed by Flex.
- If a GraphicElement needs to be redrawn all other graphic elements that share the same DisplayObject will need to redraw themselves.
- If creating a custom GraphicElement which shares a display object with other graphic elements, make sure to not clear the graphics, as this would wipe out drawings that have formerly been made by other elements.
- Properties that are applied to the DisplayObject will be reflected by all graphic elements that share the same display object. Thus, the use of individual filters, color transformations, rotation etc. require the GraphicElement to own its own display object. The standard implementation takes care of this.
- Since GraphicElement is not a display object, we need to get a reference to the graphics object for drawing. We can get this reference form the drawnDisplayObject member.
- When drawing, the offset of the graphic element relative to its display object has to be taken into account.
Creating a custom GraphicElement
In most cases, there is no need to create a custom graphic element, as the FXG Path element can be used to draw most of the required paths. The FXG Path element takes a string of drawing commands, parses it and translates it to drawing commands that are applied to the display object. This is well suited for static paths, e.g. the ones that are exported by Illustrator or Catalyst.
On the other hand, if a parameterized path is required we would need to dynamically build a string of graphic commands and Flex would need to parse them again, leading to unnecessary overhead. In such a case it might be better to create a custom GraphicElement that has the required properties and a drawing routine which takes them into account.
UPDATE: For the FXG-Path building approach see the great lightweight charts by Christophe Coenraets.
Creating a custom graphic element is straightforward, a few things must be considered though (see source in the example).
- Override the updateDisplayList() function to call the custom drawing routine, or the draw() function when using a StrokedElement or a FilledElement.
- Get the graphics object from (drawnDisplayObject as Sprite).graphics.
- Respect the coordinates of the GraphicElement relative to the one of its DisplayObject. The drawX and drawY properties of a GraphicElement store the required offset.
- Inside the drawing function do not clear the graphics since the element might use a shared display object and this would clear out graphics that have been drawn by other graphic elements.
- The GraphicElement class implements the IInvalidating interface and can therefore take part in the invalidation process. If a property is set that affects the drawing, call the required invalidation functions and do the update later in the validation pass.
Managing shared display object
The management of display objects for any GraphicElement is done by Flex via the Group that owns the element. In most cases there is no need to care about this, but if required, Flex offers some ways to indicate if a shared DisplayObject is ok to use or if an own display object is needed.
- Use needsDisplayObject() to indicate if a GraphicElement needs his own display object. The default implementation checks if any properties are used that require an own display object (rotation, color transformation, belnd modes etc.).
- Likewise canShareWithNext() and canShareWithPrevious() can be used to indicate if a GraphicElement can be drawn onto the same display object as the previous/next one in the queue.
- It is tempting to use the property displaySharingMode. This does not work as expected, as Flex uses this property to manage the objects and values set manually will be overwritten.
- If Flex needs to create a display object for a graphic element it calls its createDisplayObject() function. The default implementation returns an object of type InvalidatingSprite that implements the ISharedDisplayObject interface.
The purpose of the GraphicElement is to draw and manage graphics in an optimized manner (by sharing display objects). It is not an interactive element, the element is embedded within a Spark container and interaction should be done at a higher level.
To enable interactivity on a graphic element directly, a couple of things need to be done, which in most cases is actually bad practice. However, since this post is about inspecting the GraphicElement, let’s still see what needs to be done do to get interaction on a graphic element.
As mentioned above, the GraphicElement uses a DisplayObject to draw its graphics. The DisplayObject used by a GraphicElement is an InvalidatingSprite (extends from Sprite) that has the mouseEnabled and mouseChildren properties set to false. To get interactivity in a way that makes sense, we’d need to enable interaction on the InvalidatingSprite and disable display object sharing, as we do not want interaction to be affected by other graphic elements that share the same display object.
To turn on interactivity and to make sure interaction is not shared with other elements, we will need to
- override the createDisplayObject() function and
- set the mouseEnbled and mouseChildren properties of the returned DisplayObject to true
- add any listeners to the DisplayObject
- override needsDisplayObject() and return true
- likewise override canShareWithNext() and canShareWithPrevious() and return false
You’ll find the source code for this in the example. View source is enabled.
This of course undermines the attempt of Flex for optimization when drawing graphic elements. If interaction is needed directly on a custom graphic object it might be better to use a SpriteVisualElement that is in fact an interactive object. See the example of a ruler implemented as a SpriteVisualElelement.
The GraphicElement is Spark’s key class to do optimized drawing. If a parameterized path needs to be drawn that updates often, it might be a better choice to create a custom GraphicElement instead on relying on the FXG-Path element. Interactivity can be enforced but it is better to handle it on higher levels. If you need a really light weight visual element, that offers interaction and can still take part in the Spark layout process consider using SpriteVisualElement.
For more information on traversing GraphicElements (or IVisualElements in general) read this excellent write-up by Bill White.