Chapter 4. Graphics
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.
Sprite
VisualElement
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.
4.1. Size and Position a Graphic Element
Solution
Add graphic elements to a Graphic
display element and modify the
viewWidth
and viewHeight
properties along with the inherited
size and translation properties.
Discussion
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.
4.2. Use Path to Draw a Shape with Stroke and Fill
Solution
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.
Discussion
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 Filled
Element
, 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.
4.3. Display Text in a Graphic Element
Solution
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.
Discussion
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—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. The examples in this recipe, however, should serve as a starting point for providing richly formatted text in graphics.
4.4. Display Bitmap Data in a Graphic Element
Solution
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.
Discussion
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 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>
4.5. Display Gradient Text
Solution
Apply a gradient fill to a FilledElement
-based element and apply a
RichText
element as the mask for a
graphic.
Discussion
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:
<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.
4.6. Apply Bitmap Data to a Graphic Element as a Mask
Problem
You want to take advantage of the alpha transparency or luminosity of a bitmap when applying a mask to a graphic element.
Solution
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.
Discussion
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, 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.
4.7. Create a Custom Shape Element
Problem
You want to create a custom graphic element and modify the drawing rules based on specific properties.
Solution
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.
Discussion
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
Graphic
Element
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 shows the end result.
4.8. Create a Custom Standalone Graphic Component
Solution
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.
Discussion
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.
4.9. Define and Reuse Graphic Symbols
Problem
You want to create a library of common graphic symbols that can be used multiple times within an application.
Solution
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.
Discussion
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 ); }
Get Flex 4 Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.