Avg. Rating 5.0

Problem

You want to customize the look and feel of a container that holds visual elements or data items.

Solution

Use either a SkinnableContainer or a BorderContainer as a container for visual child elements and a SkinnableDataContainer as a container for data items, and modify the available style properties.

Detailed explanation

The Group and DataGroup containers are considered lightweight containers and as such do not support skinning or expose style properties. To customize the look of a container, the Flex 4 SDK offers the SkinnableContainer, BorderContainer, and SkinnableDataContainer classes. Which you use depends on the type of content provided to the container. The SkinnableContainer and BorderContainer take instances of IVisualElement as child elements; think of them as Group containers that support skinning. The SkinnableDataContainer can be considered a DataGroup container that supports skinning and uses item renderers to display visual representations of data items.

BorderContainer is actually a subclass of SkinnableContainer and is a convenient container to use if you want to apply border and background styles directly without applying a custom skin. You can set border styles (such as cornerRadius and borderColor) inline within the MXML declaration for the container, or through Cascading Style Sheet (CSS) style declarations. The following example demonstrates setting the border styles of a BorderContainer for IVisualElement children:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderColor="#000000"
                   borderWeight="2" borderStyle="inset">
 
    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />
 
</s:BorderContainer>

Alternatively, you can supply an IStroke instance for the borderStroke property of a BorderContainer. The following example sets a borderStroke on a BorderContainer through MXML markup:

<s:BorderContainer width="200" height="200"
                   cornerRadius="10" borderStyle="inset">

    <s:layout>
        <s:VerticalLayout paddingLeft="5" paddingTop="5" paddingBottom="5"
                          paddingRight="5" horizontalAlign="justify" />
    </s:layout>

    <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
    <s:Button label="click me" />

    <s:borderStroke>
        <s:SolidColorStroke color="#0000" weight="2" />

    </s:borderStroke>

</s:BorderContainer>

The s:SolidColorStroke element supplied as the borderStroke for the BorderContainer overrides any previously declared color or weight style properties. However, because the IStroke interface does not expose a cornerRadius or borderStyle property, some border style properties need to be provided directly to the BorderContainer if they are desired. Because BorderContainer is an extension of SkinnableContainer, skinning is also supported along with the border convenience styles.

Custom skin classes are set on the SkinnableContainerBase-based containers (such as BorderContainer) through the skinClass property, whose value can be set either inline using MXML markup or via CSS, because skinClass is considered a style property. When you create a custom skin for a container, the skin is entering into a contract with the container to provide the necessary skin parts and states for the host. These skin parts are referenced using an agreed-upon id property value and, depending on the type of skinnable container, relate to content layers for visual elements.

To create a custom skin for a skinnable container, extend the spark.skins.SparkSkin class and declare the HostComponent metadata and necessary state and skin part elements. The following is an example of a custom skin fulfilling a contract to be applied to a SkinnableContainer:

<s:SparkSkin name="CustomGroupSkin"
             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:Metadata>
        [HostComponent("spark.components.SkinnableContainer")]
    </fx:Metadata>

    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:LinearGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:LinearGradient>
        </s:fill>
        <s:fill.disabled>
            <s:RadialGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:RadialGradient>
        </s:fill.disabled>
    </s:Rect>

    <s:Group id="contentGroup"
             width="100%" height="100%"
             left="10" right="10" top="10" bottom="10">

        <s:layout>
            <s:VerticalLayout horizontalAlign="justify" />
        </s:layout>
 
    </s:Group>

</s:SparkSkin>

The CustomGroupSkin declares the type of container component that the skin will be applied to within the [HostComponent] metatag. In this example the host component is a SkinnableContainer, and as such contains a Group container with the id property value of contentGroup. The contentGroup property of SkinnableContainer is considered a skin part and represents the content layer on which visual child elements are drawn. Along with the host component and contentGroup, contractual states are declared to represent the enabled and disabled visual states of the container. You can declare additional states as needed, but at a minimum, normal and disabled need to be added to the available states to support the enabled property of the container. Styles can be applied to elements based on these states, as is shown using inline dot notation for the fill type of the s:Rect element.

To apply the custom skin to a SkinnableContainer instance, set the skinClass style property to a Class reference either inline or using CSS. The following example applies the CustomGroupSkin skin inline using a fully qualified class name:

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

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:SkinnableContainer id="container"
                          width="200" height="200"
                          skinClass="com.oreilly.f4cb.CustomGroupSkin">

        <s:Label text="Lorem ipsum dolor sit amet consectetur adipisicing elit." />
        <s:Button label="button (1)" />
        <s:DropDownList />

     </s:SkinnableContainer>

    <s:Button label="enable container"
              click="{container.enabled=!container.enabled}" />

</s:Application>

When applying a custom skin, the layout can be set within the skin class (as in this example). It should be noted, however, that if a layout is applied to a container directly in MXML markup, that layout will override any layout supplied to the content layer declared in the skin.

Creating a custom skin for a SkinnableDataContainer that uses data elements to represent children is similar to creating a custom skin for a SkinnableContainer instance. The difference between the two involves the type of host component declaration and skin part reference. The following custom skin declares the host component references as the SkinnableDataContainer class and contains a DataGroup container with the reference id of dataGroup:

<s:SparkSkin name="CustomDataGroupSkin"
             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:Metadata>
        [HostComponent("spark.components.SkinnableDataContainer")]
    </fx:Metadata>

    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Rect width="100%" height="100%">
        <s:fill>
            <s:LinearGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:LinearGradient>
        </s:fill>
        <s:fill.disabled>
            <s:RadialGradient>
                <s:entries>
                    <s:GradientEntry color="0xFF0000" />
                    <s:GradientEntry color="0x00FF00" />
                    <s:GradientEntry color="0x0000FF" />
                </s:entries>
            </s:RadialGradient>
        </s:fill.disabled>
    </s:Rect>

    <s:Scroller width="100%" height="100%">
        <s:DataGroup id="dataGroup"
                     width="100%" height="100%">

            <s:layout>
                <s:VerticalLayout paddingLeft="10" paddingRight="10"
                                  paddingTop="10" paddingBottom="10" />
            </s:layout>

         </s:DataGroup>
    </s:Scroller>

</s:SparkSkin>

The CustomDataGroupSkin fulfills a contract with SkinnableDataContainer to provide a DataGroup instance as the content layer for data elements supplied to the skinnable container. With the host component metatag and necessary states declared, the custom skin is applied to a SkinnableDataContainer through the skinClass style property:

<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:Declarations>
        <fx:String id="txt">
            Lorem ipsum dolor sit amet consectetur adipisicing elit.
        </fx:String>
    </fx:Declarations>

    <s:layout>
        <s:VerticalLayout />
    </s:layout>

    <s:SkinnableDataContainer id="container" width="200" height="200"
                              itemRenderer="spark.skins.spark.DefaultItemRenderer"
                              skinClass="com.oreilly.fcb4.CustomDataGroupSkin">

        <s:dataProvider>
            <s:ArrayCollection 
            id="collection" source="{txt.split(' ')}" />
        </s:dataProvider>

    </s:SkinnableDataContainer>

    <s:Button label="enable container"
              click="{container.enabled=!container.enabled}" />

</s:Application>

These examples demonstrate the basic contractual agreement that custom skins must fulfill when working with SkinnableContainerBase-based containers.

This recipe was originally contributed by  Todd Anderson as part of O'Reilly's Flex 4 Cookbook.


+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes