You want to customize the look and feel of a container that holds visual elements or data items.
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.
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.
+