This excerpt is from Flex 4 Cookbook. This highly practical book contains hundreds of tested recipes for developing interactive Rich Internet Applications. You'll find everything from Flex basics to solutions for working with visual components and data access, as well as tips on application development, unit testing, and Adobe AIR. Each recipe features a discussion of how and why it works, and many of them offer sample code that you can put to use immediately.
With the introduction of the Spark architecture in the Flex 4 SDK,
graphics have become first-class citizens and can be sized and positioned
within a layout along with the other elements in the container’s display
list. As in previous versions of the SDK, vector graphics are rendered using
the drawing API of a read-only flash.display.Graphics object held on the lowest
layer of a Sprite-based element. Yet for
Flex 4, the concept has been given an overhaul. Now display objects are
created and held internally by GraphicElement-based elements to render graphics,
providing a level of abstraction that allows for graphics to be treated the
same as any other visual element within a layout.
Along with this new graphical rendering concept, Flex 4 incorporates the Flash XML Graphics (referred to as FXG) format, which is a readable vector graphics format that is interchangeable between multiple software tools and does not require knowledge of the ActionScript language or MXML markup.
A FXG fragment is a grouping of graphical elements, such as shapes, text, and raster images, along with optional masking and filters that can be contained inline in MXML or within a FXG document with the .fxg file extension. FXG is a subset of MXML and does not include the ability to reference external classes or respond to runtime events, such as data binding and state changes. However, FXG fragments can be declared inline in MXML to take advantage of such features, which most skin classes in the Spark architecture employ.
The declaration of FXG fragments within FXG and MXML documents is
similar, although the namespace scope and the available and required
attributes of the graphic elements differ. The root node of a FXG document
must be declared as a Graphic type with
the required version and xmlns attributes. The following snippet is an
example of a FXG 2.0 document (the current version at the time of this
writing):
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
<Group>
<Rect width="100" height="100">
<fill>
<SolidColor color="#DDDDDD" />
</fill>
</Rect>
<RichText>
<content>Hello World!</content>
</RichText>
</Group>
</Graphic>With the namespace properly scoped, graphic elements can be added to
the document singularly or wrapped in a <Group> element. The declared node names for
graphic elements in a FXG document, such as Rect and BitmapImage, are the same as those available in
the Flex 4 SDK’s spark.primitives
package. A RichText element is also
available for FXG; it can be found in the spark.components package of the SDK.
A FXG document fragment can be added to the display list of a MXML
container similarly to any other component, either through markup with the
proper namespace scope or using the content API in ActionScript. However,
there is no API available in the Flex 4
SDK to load a FXG document with the .fxg file
extension and add it to the display
list at runtime. When a FXG document fragment is declared in an
application, the graphical data is
compiled into the application and wrapped in a spark.core.SpriteVisualElement
instance so it can be handled like any other visual element by the layout
delegate of the target container.
FXG fragments can also be declared directly in MXML in an application
with the Spark namespace declared. Scoped to the Spark namespace, GraphicElement-based elements from the spark.primitives package and the spark.components.RichText element of the Flex 4
SDK can be added in markup along with other visual elements, as in the
following:
<s:Graphic>
<s:Rect width="100" height="100">
<s:fill>
<s:SolidColor color="#DDDDDD" />
</s:fill>
</s:Rect>
<s:RichText text="Hello FXG!" />
</s:Graphic>Graphic elements declared in MXML have more properties than elements declared in FXG documents and can take advantage of MXML concepts available to other visual elements, such as data binding and runtime styling. Although runtime access of elements and properties of a FXG fragment declared in MXML markup can prove to be a valuable asset in some applications, it should be noted that this approach does add more overhead than using a FXG document that is compiled in and rasterized as a graphic element.
The following recipes will show you how these new elements work for both FXG and MXML documents.
Add graphic elements to a Graphic display element and modify the
viewWidth and viewHeight properties along with the inherited
size and translation properties.
The Graphic display element is
an extension of Group and serves as a
wrapper to contain graphic elements. When you create a FXG document with
a .fxg file extension, the Graphic element must be the root tag of the
document. When declaring a <Graphic> element in MXML markup, the
element can be placed in a container as long as it is scoped to the
proper namespace.
Aside from the inability to specify a layout delegate, most
inherited properties of Group can be
applied to a Graphic instance, which
also exposes a few specific properties of its own: version, viewWidth, and viewHeight. The version property specifies the target FXG
version for the Graphic. This
property is not required when declaring the <Graphic> element in MXML, but it is
necessary when creating a FXG document.
The viewWidth and viewHeight properties specify the size at
which to render the element within the container’s layout. Just as with
setting the viewport bounds of a Group, specifying viewWidth and viewHeight values for a Graphic element does not inherently clip the
visible area of the element. If you want parts of the graphic that
extend beyond the bounds of the view to be visible, you must also wrap
the Graphic element in a Scroller instance, as in the following
snippet:
<s:Scroller>
<s:Graphic viewWidth="100" viewHeight="100">
<s:Rect width="500" height="500">
<s:fill>
<s:SolidColor color="0x333333" />
</s:fill>
</s:Rect>
</s:Graphic>
</s:Scroller>>In this example, although the Rect graphic element is larger than the view
size specified by the Graphic
container, only a portion of the graphic (the top-left corner, extending
100 pixels along the x and y
axes) is visible. However, because the Graphic element, which is considered an
implementation of IViewport, has been
wrapped by a Scroller instance to
enable scrolling, it’s possible to view the rest of the
graphic.
The viewWidth and viewHeight properties differ from the width and height properties inherited from Group; when they are set, width and height scale the graphics in the Graphic control as opposed to modifying the
viewport. To demonstrate how the graphic element grouping is scaled when
the width and height properties are specified, the following
example contains a RichText element
as well as the Rect:
<s:Graphic width="100" height="100">
<s:Rect width="300" height="300">
<s:fill>
<s:SolidColor color="0x333333" />
</s:fill>
</s:Rect>
<s:RichText color="#FFFFFF"
horizontalCenter="0" verticalCenter="0">
<s:span>Graphic Example</s:span>
</s:RichText>
</s:Graphic>When rendered in the layout, the graphic is scaled down and
contained in a view bound to 50 pixels along the x
and y axes. By setting a noScale value for the resizeMode property, you can override the
default scaling applied to a Graphic
object when width and height property values are specified. Doing so
will, in essence, use the width and
height property values similarly to
how the viewWidth and viewHeight properties are used within a
layout.
Use the Path element and modify
the data property to specify the path
segments denoted by space-delimited command and parameter value pairs.
Supply valid IFill and IStroke implementations to the fill and stroke properties, respectively, to fill and
apply a stroke to the element.
The Path element is a graphic
element that supports fill and stroke and is used to create vector
graphics more complex than those available in the Flex 4 SDK. The shape
of the vector is constructed from a series of segments, which are
supplied as a space-delimited string of commands to the data property of the Path element. The syntax for defining a shape
typically starts with first positioning the pen at a point in the
coordinate plane and then using Line,
CubicBezier, and QuadraticBezier segments to draw the graphic.
The commands for drawing these segments are denoted by characters when
assembling the drawing data for a path. The parameter values for the
segment commands vary, though they are related to coordinate points and,
in the case of Bezier segments, may also include control
points.
The following is a list of the available commands and their usage. Specifying the uppercase version of a command causes the drawing procedure to treat the parameter values as absolute, while specifying the lowercase version causes it to consider them as relative:
The following is an example of drawing a simple polygon using line segments:
<s:Path data="M 0 0
L 100 0
L 100 100
L 0 100
Z">
<s:stroke>
<s:SolidColorStroke color="#333333" caps="square" joints="miter" />
</s:stroke>
<s:fill>
<s:SolidColor color="#00CCFF" />
</s:fill>
</s:Path>First the pen is moved to 0,0 along the coordinate plane using the
M command. It is then moved using
Line segments to draw a polygon with
a width and height of 100 pixels and closed using the Z command. Because Path is an extension of FilledElement, a stroke and a fill can be
applied to the element.
A polygon can also be created using the H and V
commands to move the pen along the x and
y axes, respectively, as in the following
example:
<s:Path data="M 0 0
H 100
V 100
H 0
Z">
<s:stroke>
<s:SolidColorStroke color="#333333" caps="square" joints="miter" />
</s:stroke>
<s:fill>
<s:SolidColor color="#00CCFF" />
</s:fill>
</s:Path>It is important to note that using the uppercase H and V
drawing commands treats the parameter values as absolute. That is, if
the pen is originally moved to a coordinate other than 0,0, the line
segments will still be drawn to 100 pixels along the
x and y axes starting from
0,0, not from the point specified in the M command. To have the line segments treated
as relative, use lowercase commands, as in the following example:
<s:Path data="M 20 20
h 100
v 100
h −100
z">
<s:stroke>
<s:SolidColorStroke color="#333333" caps="square" joints="miter" />
</s:stroke>
<s:fill>
<s:SolidColor color="#00CCFF" />
</s:fill>
</s:Path>In this example, a series of 100-pixel line segments are drawn
starting from the 20,20 origin specified in the M command. The result is a polygon with a
width and height of 100 pixels.
The Path element also exposes a
winding property, which allows you to
specify the fill rule for the vector graphic with respect to
intersecting or overlapping path segments. By default the winding value is evenOdd, which will render the intersection of
multiple path segments as a knockout. Let’s look at an
example:
<s:Graphic x="10" y="10">
<s:Path winding="{GraphicsPathWinding.EVEN_ODD}"
data="M 0 0
L 100 0
L 100 100
L 0 100
Z
M 50 50
L 150 50
L 150 150
L 50 150
Z">
<s:stroke>
<s:SolidColorStroke color="#333333" caps="square" joints="miter" />
</s:stroke>
<s:fill>
<s:SolidColor color="#00CCFF" />
</s:fill>
</s:Path>
</s:Graphic>Here, two overlapping polygons are drawn within a Path element: first a shape is drawn at 0,0,
then the pen is moved to 50,50 and another shape is drawn. The
intersection of the two polygons at 50,50 and extended to 100 pixels
along the x-axis and 100 pixels along the
y-axis is not rendered because of the specified
evenOdd winding rule. Because each
path segment in this example is drawn in a clockwise direction, the
winding property value can be changed
to nonZero in order to fill the
intersection if needed. However, if the drawing sequence for each
polygon is different—as in the following example, which draws the first
polygon using a clockwise path and the second using a counterclockwise
path—the winding property value is
negated:
<s:Path winding="{GraphicsPathWinding.NON_ZERO}"
data="M 0 0
L 100 0
L 100 100
L 0 100
Z
M 50 50
L 50 150
L 150 150
L 150 50
Z">
<s:stroke>
<s:SolidColorStroke color="#333333" caps="square" joints="miter" />
</s:stroke>
<s:fill>
<s:SolidColor color="#00CCFF" />
</s:fill>
</s:Path>Drawing paths using evenOdd
winding or overlapping counterclockwise and clockwise paths might look
like Figure 4.1, “An example of a knockout within a Path element”.
Use the RichText element and
either supply formatted text as the content property value or specify a TextFlow instance that manages textual content
to be rendered within a FXG fragment.
Included in the 2.0 version of the FXG specification is a RichText element that can be used to render
rich-formatted textual content as a vector graphic. The RichText element makes use of the Text Layout
Framework (TLF)—discussed in more detail in Chapter 7, Text and TextFlows —to
offer better support for typography and layout of text, although the
resulting text is noninteractive and does not allow for scrolling or
selection.
Unlike the other representations of graphic elements in the Flex 4
SDK, such as shape paths and raster images, RichText is not a GraphicElement-based object; rather, it is an
extension of TextBase. TextBase is a UIComponent-based element that exposes a few
properties related to the display of text and supports applying CSS
styles for formatting. RichText
utilizes the TLF API in order to render styled and formatted textual
content in a TextFlow element. The
value supplied to the content
property of RichText is managed by an
instance of flashx.textLayout.elements.TextFlow, which
treats formatted text as a hierarchical tree of elements. As such,
RichText supports many tags for
properly rendering the textual content of a story, such as <div>, <p>, <span>, and <img>.
When using the RichText element
in a FXG document, the content
property is used to supply rich-formatted text, as in the following
example:
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
<RichText width="400" height="60"
columnCount="4"
fontFamily="Helvetica">
<content>
<div>
<img source='assets/icon.png' width='20' height='20' />
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit,
<span fontWeight="bold">sed diam nonummy nibh euismod tincidunt ut
laoreet dolore</span>
magna aliquam erat volutpat.
</p>
</div>
</content>
</RichText>
</Graphic>In this example, an image from a local resource is loaded and
rendered alongside textual content that is represented using the glyph
information of the Helvetica font. An image can be rendered within the
content of a RichText element in a
FXG document only at compile time. Consequently, the image path must
point to a location on the local disk from which the application is
compiled and cannot be a URL.
With width and height property values specified for the
element, a columnCount style
property—along with columnGap and
columnWidth—can be applied to render
text using multiple lines across multiple columns.
Along with enabling you to take advantage of runtime concepts such
as data binding and changes to state, defining a RichText graphic in MXML allows you to specify
a TextFlow instance to use in
rendering the textual content. The TextFlow object is the root element of a tree
of textual elements, such as spans and paragraphs. A richly formatted
string using element tags is converted into a tree structure of elements
from the flashx.textLayout.elements
package, which contains the core classes used to represent textual
content in TLF. Typically, the spark.utils.TextFlowUtil class is used to
retrieve an instance of TextFlow from
the static importFromString() and
importFromXML() methods, as in the
following example:
<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">
<fx:Script>
<![CDATA[
import spark.utils.TextFlowUtil;
[Bindable]
public var txt:String = "<div>" +
"<img source='assets/icon.png' width='20' height='20' />" +
"<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit," +
"<span fontWeight='bold'>sed diam nonummy nibh euismod tincidunt ut" +
"laoreet dolore</span>" +
"magna aliquam erat volutpat.</p></div>";
]]>
</fx:Script>
<s:Graphic x="10" y="10">
<s:RichText width="400" height="60"
columnCount="4"
fontFamily="Helvetica"
textFlow="{TextFlowUtil.importFromString(txt)}"/>
</s:Graphic>
</s:Application>The string value of the txt
property is rendered within a TextFlow object returned from the static
importFromString() method of TextFlowUtil. An instance of TextFlow can also be created and assigned to
the textFlow property of a RichText object in ActionScript. Doing so,
however, generally requires more fine-grained configuration of how the
textual content is contained for layout,
and elements from the
flashx.textLayout.elements package are added directly
using the addChild() method, as
opposed to supplying a rich-formatted string in the static convenience
method of the TextFlowUtil
class.
The Flash Text Engine (FTE) in Flash Player 10 and the ancillary classes and libraries included in the Flex 4 SDK that manage the rendering of textual content (such as TLF) are too complex to discuss in a single recipe and are covered in more detail in Chapter 7, Text and TextFlows. The examples in this recipe, however, should serve as a starting point for providing richly formatted text in graphics.
Use the BitmapImage element or
supply a BitmapFill to a FilledElement-based element and set the
source property to a value of a valid
representation of a bitmap. Optionally, set the fillMode of the graphic to clip, scale, or
repeat the image data within the element.
Bitmap information from an image source can be rendered within a
graphic element in a FXG fragment. The BitmapImage element can be used to define a
rectangular region in which to render the source bitmap data, or any
FilledElement-based element can be
assigned a BitmapFill to render the
data within a custom filled path. fillMode is a property of both BitmapImage and BitmapFill that defines how the bitmap data
should be rendered within the element. The values available for fillMode are enumerated in the BitmapFillMode class and allow for clipping,
scaling, and repeating the bitmap data within the defined bounds of the
element. By default, the fillMode
property is set to a value of scale,
which fills the display area of an element with the source bitmap
data.
The following example demonstrates using both the BitmapImage element and BitmapFill within a MXML fragment to display
bitmap information:
<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">
<fx:Script>
<![CDATA[
import mx.graphics.BitmapFillMode;
]]>
</fx:Script>
<s:Graphic>
<s:Group>
<s:layout>
<s:HorizontalLayout />
</s:layout>
<s:BitmapImage id="img" width="450" height="400"
source="@Embed('assets/icon.png')" />
<s:Ellipse id="imgEllipse" width="450" height="400">
<s:fill>
<s:BitmapFill id="imgFill"
fillMode="{BitmapFillMode.REPEAT}"
source="@Embed('assets/icon.png')" />
</s:fill>
</s:Ellipse>
</s:Group>
</s:Graphic>
</s:Application>The source property of a
BitmapImage element or the BitmapFill of an element, when declared in
MXML, can point to various graphic resources. The source could be a
Bitmap object, a BitmapData object, any instance or class
reference of a DisplayObject-based
element, or an image file specified using the @Embed directive. If a file reference is used,
the image file path must be relative as it is compiled in; there is no
support for runtime loading of an image when using FXG elements in MXML
markup.
Figure 4.2, “Examples of rendering a raster image in a graphic” shows a few examples of effects you can achieve using the various graphic elements and fill modes. On the left, an image is loaded and resized to fill a rectangle shape. On the right, the same image is loaded into an ellipse shape and repeated at its original size to fill the shape.
The source property value for
an element rendering bitmap data in a FXG document can point either to a
relative file path for an image resource, or to a URL. Bitmap
information is compiled into the graphic element within the FXG
document, and such runtime concepts as updating the source based on
loaded graphic information are not applicable.
The following is an example of supplying a URL to the source property of a BitmapImage element within a FXG
document:
<!-- MyBitmapGraphic.fxg -->
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
<BitmapImage width="600" height="150" fillMode="repeat"
source="http://covers.oreilly.com/images/9780596529857/bkt.gif"
/>
</Graphic>Supplying a URL for the bitmap fill of an element is not permitted
in a FXG fragment within MXML markup. However, graphics declared in MXML
take advantage of various runtime concepts, including responding to
state changes, data binding, and (with regard to displaying bitmap
information) loading graphic resources and updating the source of a
bitmap element at runtime. The following example demonstrates setting
the source property of a BitmapImage to a Bitmap instance at runtime alongside rendering
the graphic element of a FXG document:
<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:f4cb="com.oreilly.f4cb.*"
creationComplete="handleCreationComplete();">
<fx:Script>
<![CDATA[
import mx.graphics.BitmapFillMode;
private function handleCreationComplete():void
{
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
handleLoadComplete);
loader.load( new URLRequest(
'http://covers.oreilly.com/images/9780596529857/bkt.gif' ) );
}
private function handleLoadComplete( evt:Event ):void
{
var bmp:Bitmap = ( evt.target as LoaderInfo ).content as Bitmap;
img.source = bmp;
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:Graphic>
<s:Group>
<s:layout>
<s:HorizontalLayout />
</s:layout>
<s:BitmapImage id="img"
width="450" height="400"
fillMode="{BitmapFillMode.SCALE}" />
<f4cb:MyBitmapGraphic />
</s:Group>
</s:Graphic>
</s:Application>Apply a gradient fill to a FilledElement-based element and apply a
RichText element as the mask for a
graphic.
The color style property of the
RichText element takes a single color
component and does not support multiple gradient entries. You can,
however, render noninteractive text in a linear or radial gradient by
using the text graphic as a mask applied to a filled path.
The following is an example of applying a RichText element as a mask for a graphic
element that renders a rectangular gradient, shown in Figure 4.3, “Example of gradient text using a RichText element as a
mask”:
<s:Graphic maskType="alpha">
<s:Rect width="{textMask.width}" height="{textMask.height}">
<s:fill>
<s:LinearGradient rotation="90">
<s:entries>
<s:GradientEntry color="#000000" />
<s:GradientEntry color="#DDDDDD" />
</s:entries>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:mask>
<s:RichText id="textMask" fontFamily="Arial" fontSize="20">
<s:content>Hello World!</s:content>
</s:RichText>
</s:mask>
</s:Graphic>With the maskType property of
the Graphic element set to alpha, the RichText element renders using the gradient
values of the child s:Rect element
based on the glyph information of the text. Binding the dimensions of
the RichText instance to the width and height properties of the Rect element ensures the rendering of the full
gradient when the textual content is applied to the graphic, even though
it is a mask.
You want to take advantage of the alpha transparency or luminosity of a bitmap when applying a mask to a graphic element.
Apply an Image element or a
Group-wrapped BitmapImage to a Graphic as the mask source and set the desired
maskType property value. Depending on
the maskType property value,
optionally set the luminosityClip and
luminosityInvert properties of the
Graphic element as well.
The mask property of Graphic, which is inherited from its extension
of Group, is typed as a DisplayObject instance. You cannot, therefore,
directly apply a GraphicElement-based
element (such as Rect or BitmapImage) as a mask for a GroupBase-based element. You can, however,
wrap graphic elements in a Group
object and apply them as a mask. Likewise, any DisplayObject-based element, including the
visual elements from the MX set, can be applied as a mask source for a
Graphic element.
By default, masking of content within a GroupBase-based element is performed using
clipping. With the maskType property
value set to clip, the content is
rendered based on the area of the mask source. Along with clip, there are two other valid values for the
maskType property of a GroupBase-based element when applying a mask:
alpha and luminosity. When you assign an alpha mask type, the alpha values of the mask
source are used to determine the alpha and color values of the masked
content. Assigning a luminosity mask is similar in that the content’s
and mask source’s alpha values are used to render the masked pixels, as
well as their RGB values.
The following example applies all three valid maskType property values to a Graphic element that is masked using an image
containing some alpha transparency:
<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"
creationComplete="handleCreationComplete();">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import spark.core.MaskType;
[Bindable] public var masks:ArrayCollection;
private function handleCreationComplete():void
{
masks = new ArrayCollection( [ MaskType.CLIP,
MaskType.ALPHA,
MaskType.LUMINOSITY ] );
maskList.selectedIndex = 0;
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:DropDownList id="maskList" dataProvider="{masks}" />
<s:Graphic id="group" maskType="{maskList.selectedItem}">
<s:Rect width="320" height="320">
<s:fill>
<s:LinearGradient>
<s:entries>
<s:GradientEntry color="#000000" />
<s:GradientEntry color="#DDDDDD" />
</s:entries>
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:mask>
<s:Group>
<s:BitmapImage source="@Embed('/assets/alpha_bitmap.png')" />
</s:Group>
</s:mask>
</s:Graphic>
<s:Group enabled="{maskList.selectedItem==MaskType.LUMINOSITY}">
<s:layout>
<s:HorizontalLayout />
</s:layout>
<s:CheckBox selected="@{group.luminosityInvert}" label="invert" />
<s:CheckBox selected="@{group.luminosityClip}" label="clip" />
</s:Group>
</s:Application>With the maskType property of
the Graphic element set to clip, the gradient-filled Rect is clipped to the rectangular bounds of
the embedded image. With the maskType
set to alpha, the alpha values of the
bitmap are used to render the masked pixels. When luminosity is selected as the maskType, two s:CheckBox controls are enabled, allowing you
to set the luminosityInvert and
luminosityClip properties of the
Graphic element. If you are using an
image that supports alpha transparency you might see something similar
to Figure 4.4, “Example of applying an image with alpha transparency to a
graphic element as a mask”, which allows you to
play with the different types of masks.
The luminosityInvert and
luminosityClip properties are only
used when the maskType is set to
luminosity. With both property values
set to false (the default), the
pixels of the content source and the mask are clipped to the bounds of
the image area and are blended. A true value for luminosityInvert inverts and multiplies the
RGB color values of the source, and a true value for luminosityClip clips the masked content based
on the opacity values of the mask source.
You want to create a custom graphic element and modify the drawing rules based on specific properties.
Extend FilledElement, override
the draw() method to render the
custom vector graphic, and optionally override the measuredWidth and measuredHeight accessors in order to properly
lay out the element.
The spark.primitives.supportClasses.GraphicElement
class is a base class for all graphic elements, including raster images,
text, and shapes. GraphicElement
exposes the necessary properties to size and position elements within a
layout delegate, and essentially manages the display object that
graphics are drawn into, and onto which
transformations and filters are applied. StrokedElement is a subclass of
GraphicElement that exposes the ability to apply
a stroke to a vector shape. FilledElement is a subclass of StrokedElement that provides the ability to
apply a fill to a vector shape and can be extended to customize the
drawing paths of a custom shape.
The stroke and fill applied to a FilledElement are implementations of IStroke and IFill, respectively, and standard classes to
apply to a shape as strokes and fills can be found in the mx.graphics package. Typically, the initiation
and completion of rendering the stroke and fill of a shape are handled
in the protected beginDraw() and
endDraw() methods. When extending the
FilledElement class to create a
custom shape element, the protected draw() method is overridden in order to apply
drawing paths to a Graphics object using the drawing API, as
in the following example:
package com.oreilly.f4cb
{
import flash.display.Graphics;
import spark.primitives.supportClasses.FilledElement;
public class StarburstElement extends FilledElement
{
private var _points:int = 5;
private var _innerRadius:Number = 50;
private var _outerRadius:Number = 100;
override public function get measuredWidth():Number
{
return _outerRadius * 2;
}
override public function get measuredHeight():Number
{
return _outerRadius * 2;
}
override protected function draw( g:Graphics ):void
{
var start:Number = ( Math.PI / 2 );
var step:Number = Math.PI * 2 / _points;
var rad:Number = outerRadius;
var inRad:Number = innerRadius;
var angle:Number = start;
var sangle:Number = angle - step / 2;
var x:Number = rad * Math.cos( sangle ) + rad;
var y:Number = rad * Math.sin( sangle ) + rad;
g.moveTo( x,y );
x = inRad * Math.cos( angle ) + rad;
y = inRad * Math.sin( angle ) + rad;
g.lineTo( x, y );
for( var i:int = 1; i < points; i++ )
{
angle = start + ( i * step );
sangle = angle - step / 2;
g.lineTo( rad * Math.cos( sangle ) + rad,
rad * Math.sin( sangle ) + rad );
g.lineTo( inRad * Math.cos( angle ) + rad,
inRad * Math.sin( angle ) + rad );
}
}
[Bindable]
public function get points():int
{
return _points;
}
public function set points( value:int ):void
{
_points = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
[Bindable]
public function get innerRadius():Number
{
return _innerRadius;
}
public function set innerRadius( value:Number ):void
{
_innerRadius = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
[Bindable]
public function get outerRadius():Number
{
return _outerRadius;
}
public function set outerRadius( value:Number ):void
{
_outerRadius = value;
invalidateSize();
invalidateDisplayList();
invalidateParentSizeAndDisplayList();
}
}
}The StarburstElement created in
this example is an extension of FilledElement and overrides the draw() method in order to render a starburst
shape in the supplied Graphics object. draw(), along with beginDraw() and endDraw(), is invoked upon each request to
update the display list. The line segments to be drawn are determined
using the points, innerRadius, and outerRadius properties of StarburstElement, which each invoke internal
methods to update the size and display list of the element and its
parent element. Doing so ensures that the element is properly laid out
in a container. The measuredWidth and
measuredHeight accessors are also
overridden to return an accurate size for the element used by the
layout.
The following example demonstrates adding the custom StarburstElement element to the display list
and provides HSlider controls to
modify the properties of the element at runtime:
<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:f4cb="com.oreilly.f4cb.*">
<s:layout>
<s:VerticalLayout />
</s:layout>
<f4cb:StarburstElement id="star">
<f4cb:fill>
<s:SolidColor color="#333333" />
</f4cb:fill>
<f4cb:stroke>
<s:SolidColorStroke color="#FF00FF" />
</f4cb:stroke>
</f4cb:StarburstElement>
<s:HSlider id="ptSlider"
minimum="3" maximum="20"
value="{star.points}"
change="star.points=ptSlider.value" />
<s:HSlider id="inSlider"
minimum="5" maximum="50"
value="{star.innerRadius}"
change="{star.innerRadius=inSlider.value}" />
<s:HSlider id="outSlider"
minimum="55" maximum="100"
value="{star.outerRadius}"
change="{star.outerRadius=outSlider.value}" />
</s:Application>Figure 4.5, “Example of a custom graphic element with attributes available for modification at runtime” shows the end result.
Figure 4.5. Example of a custom graphic element with attributes available for modification at runtime
Create a FXG fragment within a root <Graphic> node and save it as a
standalone FXG or MXML document with the required document attributes
declared.
The structure and availability of elements is similar when
creating graphics within a FXG or a MXML document. In some cases, such
as with declaring library definitions wrapped in a <Group> element within a FXG document,
the node structure may vary, yet both types of documents contain a
fragment of graphical information declared in a root <Graphic> node and can be added to an
application at compile time or at runtime.
The required attributes for the root <Graphic> node of a FXG document (with
the .fxg
extension) are version and xmlns, as shown in the following
snippet:
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
A Graphic element is similar to
a Group element, in that children are
defined declaratively to make up the visual representation of the FXG
fragment. Along with the declaration of a mask and a library of reusable
symbols, any valid graphic element (Rect, BitmapGraphic, RichText, etc.) can be declared, either
wrapped in a Group element or as a
standalone element. The following is an example of the markup of a FXG
document:
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
<Rect width="100" height="100">
<fill>
<RadialGradient>
<GradientEntry color="#FFFFFF" />
<GradientEntry color="#000000" />
</RadialGradient>
</fill>
<stroke>
<SolidColorStroke color="#333333" />
</stroke>
</Rect>
</Graphic>A Graphic object declared in
MXML, whether as a standalone graphic or an inline fragment within a
document, must be scoped to the Spark namespace declared within the
document. The following is an example of a standalone graphic component
declared as a MXML document:
<s:Graphic name="CustomMXMLGraphic"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:Rect width="100" height="100">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="#FFFFFF" />
<s:GradientEntry color="#000000" />
</s:LinearGradient>
</s:fill>
<s:stroke>
<s:SolidColorStroke color="#333333" />
</s:stroke>
</s:Rect>
</s:Graphic>Standalone graphic elements saved as FXG and MXML documents are
added to the display list in the same manner, by declaring the element
scoped to the namespace representing the package directory in which the
document resides. The following example demonstrates adding the previous
two examples, saved as CustomFXGGraphic.fxg and
CustomMXMLGraphic.mxml,
respectively, to a MXML document:
<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:f4cb="com.oreilly.f4cb.*">
<s:layout>
<s:VerticalLayout />
</s:layout>
<f4cb:CustomMXMLGraphic />
<f4cb:CustomFXGGraphic />
</s:Application>Though the two graphical fragments are saved with different file
extensions, they are declared similarly to each other and to other
component declarations in MXML markup, and are scoped to the f4cb namespace, which points to a directory
relative to the root of the Application document.
The decision to use a graphic element scoped to the FXG namespace, as in the first example, or scoped to the Spark namespace, as in the second, depends on the role of the graphic element within the lifespan of the application in which it is used. Because FXG is a subset of MXML, graphic elements scoped to the FXG namespace and saved as .fxg documents have a limited property list and cannot take full advantage of features available to graphic fragments in MXML markup. Graphic elements declared in MXML can be treated the same as any other elements within the document markup and can reference external classes, respond to changes of state and data binding at runtime, and have their properties modified by transitions. Although using FXG fragments in MXML has its benefits, more memory is used to store references that may be accessed at runtime.
You want to create a library of common graphic symbols that can be used multiple times within an application.
Declare symbols as Definition
instances within a Library tag and
assign a unique name property value
to each Definition to be used as the
element type in a FXG fragment.
Symbol definitions are held in the Library tag of a FXG or MXML document, which
must be declared as the first child of the root tag. Singular and
grouped graphic elements can be created as symbol definitions and can be
reused multiple times throughout the document in which the containing
Library is declared. Usage of symbol
definitions in a FXG document is limited to the fragment markup, while
symbol definitions within a MXML document can be added to the display
list through markup or by using the new operator in ActionScript.
When declared in a Library
within a FXG document, symbol definitions are considered groupings of
graphic elements regardless of the number of elements declared and must
always be wrapped within a Group tag,
as in the following example:
<!-- com.oreilly.f4cb.CustomFXGCircle.fxg -->
<Graphic version="2.0" xmlns="http://ns.adobe.com/fxg/2008">
<Library>
<Definition name="Circle">
<Group>
<Ellipse width="100" height="100">
<fill>
<SolidColor color="#FFCC00" />
</fill>
</Ellipse>
</Group>
</Definition>
</Library>
<Circle />
<Circle x="100">
<filters>
<DropShadowFilter />
</filters>
</Circle>
</Graphic>The Library element is declared
as the first child of the document and is scoped to the FXG namespace
defined in the root <Graphic>
tag. The name attribute value of a
symbol definition is used to declare new instances of the symbol within
the document. Several properties are available for the instance
declarations, and transformations and filters can be applied separately
from the definition in a FXG document.
Symbol definitions declared within a library of a MXML document
differ from definition declarations in a FXG document in that a symbol
with a single graphic element does not need to be wrapped in a <Group> tag:
<fx:Library>
<fx:Definition name="MXMLCircle">
<s:Ellipse width="100" height="100">
<s:fill>
<s:SolidColor color="#00FFCC" />
</s:fill>
</s:Ellipse>
</fx:Definition>
</fx:Library>If, however, more than one graphic element makes up the symbol
definition, the elements must be wrapped in a <Group> tag. The name attribute of the symbol definition is
used, just as in a FXG document, to declare instances of the symbol
within MXML markup:
<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">
<fx:Library>
<fx:Definition name="MXMLCircle">
<s:Ellipse width="100" height="100">
<s:fill>
<s:SolidColor color="#00FFCC" />
</s:fill>
</s:Ellipse>
</fx:Definition>
</fx:Library>
<s:layout>
<s:VerticalLayout />
</s:layout>
<fx:MXMLCircle />
<fx:MXMLCircle x="100">
<fx:filters>
<fx:Array>
<s:DropShadowFilter />
</fx:Array>
</fx:filters>
</fx:MXMLCircle>
</s:Application>Upon declaration of a symbol within the document, properties (such as those related to transformations and filters) can be reset from any values attributed in the definition for the symbol.
Libraries and definitions are a convenient way to declare graphic
symbols that you can then reference and use multiple instances of within
a FXG document. Symbol definitions can even be used in other symbol
definitions declared in a Library. As
mentioned earlier, by using the name
property of a symbol definition along with the new operator, new instances of the graphic
symbol can also be instantiated at runtime using ActionScript, as in the
following example:
private function addSymbolFromLibrary():void
{
var mySymbol:IVisualElement = new FXGCircle() as IVisualElement;
addElement( mySymbol );
}If you enjoyed this excerpt, buy a copy of Flex 4 Cookbook.
Copyright © 2009 O'Reilly Media, Inc.