O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Graphics: Chapter 4 - Flex 4 Cookbook

by Joshua Noble, Todd Anderson, Marco Casario, Garth Braithwaite
Flex 4 Cookbook

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.

buy button

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.

Size and Position a Graphic Element

Problem

You want to control the size and position of a grouping of graphical elements.

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.

Use Path to Draw a Shape with Stroke and Fill

Problem

You want to draw a custom complex shape with a fill and a stroke.

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:

M/m

Moves the pen to the specified position to begin drawing

L/l

Draws a line segment from the current position to the specified coordinate

C/c

Draws a curve segment to the specified coordinate based on supplied control points

Q/q

Draws a curve segment to the specified coordinate based on a single control point

H/h

Draws a horizontal line

V/v

Draws a vertical line

Z/z

Closes the path

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”.

Figure 4.1. An example of a knockout within a Path element

An example of a knockout within a Path element

Display Text in a Graphic Element

Problem

You want to render textual content within 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, Text and TextFlowsto 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.

Display Bitmap Data in a Graphic Element

Problem

You want to display a raster image within 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, “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.

Figure 4.2. Examples of rendering a raster image in a graphic

Examples of rendering a raster image in a graphic

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>

Display Gradient Text

Problem

You want to render textual content using a gradient color.

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, “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>

Figure 4.3. Example of gradient text using a RichText element as a mask

Example of gradient text using a RichText element as a mask

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.

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, “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.

Figure 4.4. Example of applying an image with alpha transparency to a graphic element as a mask

Example of applying an image with alpha transparency to a graphic element as a mask

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.

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 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

Example of a custom graphic element with attributes available for modification at runtime

Create a Custom Standalone Graphic Component

Problem

You want to create a graphic component that can be used throughout multiple applications.

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.

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 );
}

If you enjoyed this excerpt, buy a copy of Flex 4 Cookbook.

Copyright © 2009 O'Reilly Media, Inc.