All Spark components and subcomponents that are subclasses
of SkinnableComponent can
be reskinned. Common tasks when reskinning Spark components include
adding borders, drop shadows, and transitions to a skin class.
Setting minimum sizes of a skin
A common task when creating Spark skins is to set minimum
sizes of the skin. This causes the Flex layout to not reduce the
size of a component below a pre-defined width or height. You do
this with the minWidth and minHeight properties
on the skin class’s top-level element.
In general, you should not change the values of the minimum height
and minimum width properties once they are set.
The following example creates a custom Button skin that has a
minimum width of 100 pixels and a minimum height of 100 pixels.
The default Button skin’s minimum size is 21 pixels wide by 21
pixels high. These minimums are set on the root tag of the skin
class, as the following example shows:
You might notice that in the default button skin’s class, though,
the Rect border sets its width to a value that is larger than the
minimum width on the skin:
The width property on the Rect border is used
for buttons whose size is unset. The minWidth property
on the SparkSkin is
used when the button’s width is variable, meaning that it hasn’t
been explicitly set. The previous example sets a default width and
height of 69 and 20. However, it is pinned to (left=0, right=0, top=0,
bottom=0) so that if the label is too large or the button is explicitly
sized, then this rectangle will resize.
Accessing application properties
In addition to accessing the host component’s properties,
you can access properties of the application. This is useful if
there are global settings that you want to access, or runtime information
that is passed into the application that you want available in the
skin class.
To access global variables in a custom skin, you use the FlexGlobals.topLevelApplication property.
Using this property gives you access to all global variables, including
variables that were passed into the application as flashVars variables.
The following example accesses a String that is a global variable
on the application:
The executing SWF file for the previous example is shown below:
The following is the custom skin class for this example:
<?xml version="1.0" encoding="utf-8"?>
<!-- SparkSkinning\mySkins\GlobalVariableAccessorSkin.mxml -->
<s:Skin
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
minWidth="21" minHeight="21">
<fx:Metadata>
[HostComponent("spark.components.Button")]
</fx:Metadata>
<fx:Script>
import mx.core.FlexGlobals;
[Bindable]
private var localString:String = FlexGlobals.topLevelApplication.myLabelString;
</fx:Script>
<!-- Specify one state for each SkinState metadata in the host component's class -->
<s:states>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="down"/>
<s:State name="disabled"/>
</s:states>
<s:Rect
left="0" right="0"
top="0" bottom="0"
width="69" height="20"
radiusX="2" radiusY="2">
<s:stroke>
<s:SolidColorStroke color="0x000000" weight="1"/>
</s:stroke>
</s:Rect>
<s:Label id="labelDisplay"
text="{localString}"
horizontalCenter="0" verticalCenter="1"
left="10" right="10" top="2" bottom="2">
</s:Label>
</s:Skin>
Using style properties in custom skins
Within the skin, you can get the value of certain style
properties on the host component. The component declares what styles
can be set so that they can be set inline in MXML. For example,
you can set the chromeColor property on all skins
that extend the SparkSkin class.
On other skins, such as container skins, you can set the backgroundColor.
The following example sets the color of the border inside the
skin to the same value as the Button’s color style
property:
The executing SWF file for the previous example is shown below:
The following is the skin class for this example:
<?xml version="1.0" encoding="utf-8"?>
<!-- SparkSkinning\mySkins\BorderColorButtonSkin.mxml -->
<s:Skin
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
minWidth="21" minHeight="21"
creationComplete="initSkin()">
<fx:Script>
private function initSkin():void {
/* Note that because color can change, you could override the
updateDisplayList() method and set it there so that the
color is updated in the skin when it is updated on the
host component. */
outline.color = getStyle('color');
}
</fx:Script>
<fx:Metadata>
[HostComponent("spark.components.Button")]
</fx:Metadata>
<!-- Specify one state for each SkinState metadata in the host component's class -->
<s:states>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="down"/>
<s:State name="disabled"/>
</s:states>
<s:Rect left="0" right="0" top="0" bottom="0" width="69" height="20" radiusX="2" radiusY="2">
<s:stroke>
<s:SolidColorStroke id="outline" weight="1"/>
</s:stroke>
</s:Rect>
<s:Label id="labelDisplay"
horizontalCenter="0" verticalCenter="1"
left="10" right="10" top="2" bottom="2">
</s:Label>
</s:Skin>
You can use a binding expression to set properties in the skin
based on values in the host component. The following example binds
the value of the stroke’s color property to the
host component’s color style property:
The executing SWF file for the previous example is shown below:
The following is the skin class for this example:
<?xml version="1.0" encoding="utf-8"?>
<!-- SparkSkinning\mySkins\StyleWatcherSkin.mxml -->
<s:Skin
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
minWidth="21" minHeight="21">
<fx:Metadata>
[HostComponent("spark.components.Button")]
</fx:Metadata>
<!-- Specify one state for each SkinState metadata in the host component's class -->
<s:states>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="down"/>
<s:State name="disabled"/>
</s:states>
<s:Rect left="0" right="0" top="0" bottom="0" width="69" height="20" radiusX="2" radiusY="2">
<s:stroke>
<!-- Match the border color to the button label's color. -->
<s:SolidColorStroke color="{getStyle('color')}" weight="1"/>
</s:stroke>
</s:Rect>
<s:Label id="labelDisplay"
horizontalCenter="0" verticalCenter="1"
left="10" right="10" top="2" bottom="2">
</s:Label>
</s:Skin>
This example sets the color of the stroke when the application
starts. When you click the Button, the color property
later changes while the application is running because style properties
are bindable.
For custom components, you must declare the style metadata on
the component itself so that inline MXML styling will work.
To expose a property as a style, you can also define a getter
and setter for the property. You then override the styleChanged() method
to dispatch the binding change event. In the application, you call
the setStyle() method on the instance’s skin property,
which sets the value of the style property on the skin.
The following example exposes a custom style property called borderColor on
the custom skin:
<?xml version="1.0" encoding="utf-8"?>
<!-- SparkSkinning/mySkins/StyleableBorderSkin.mxml -->
<s:Skin
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
alpha.disabled="0.5">
<fx:Script>
[Bindable("borderColorChange")]
public function get borderColor():uint {
return getStyle("borderColor");
}
public function set borderColor(value:uint):void {
setStyle("borderColor", value);
}
override public function styleChanged(styleProp:String):void {
super.styleChanged(styleProp);
if (styleProp == "borderColor" || styleProp == null)
dispatchEvent(new Event("borderColorChange"));
}
</fx:Script>
<fx:Metadata>
[HostComponent("spark.components.SkinnableContainer")]
</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="disabled" />
</s:states>
<s:Group id="contentGroup" left="0" right="0" top="0" bottom="0">
<s:layout>
<s:VerticalLayout/>
</s:layout>
</s:Group>
<s:Rect left="0" right="0" top="0" bottom="0">
<s:stroke>
<s:SolidColorStroke color="{borderColor}" weight="1"/>
</s:stroke>
</s:Rect>
</s:Skin>
In the following application, you set the color of the border
by using the ColorPicker control. This control uses the setStyle() method
to change the color of the container’s border.
The executing SWF file for the previous example is shown below:
Another way to expose style properties to the skin is to add
code to the updateDisplayList() method to manually
push style values into the skin’s graphics. This is the more efficient
method of pushing style properties to skins because it does not
rely on binding. The following example overrides the updateDisplayList() method
to push the background color’s style property:
<fx:Script>
override protected function updateDisplayList(unscaleWidth:Number, unscaledHeight:Number):void {
// Push style values into the graphics properties before calling super.updateDisplayList
backgroundFill.color = getStyle("backgroundColor");
// Call super.updateDisplayList to do the rest of the work
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
</fx:Script>
<s:Rect left="0" right="0" top="0" bottom="0">
<s:SolidColor id="backgroundFill" />
</s:Rect>
Using events in custom skins
In general, you should use states to react to user interaction.
If a skin part is removed from display list whenever the mouse hovers
over a control, then that part cannot receive mouseDown events.
Some skins, such as the Button control’s skin, block user interaction
to the skin so that the underlying control only can dispatch events.
This means that you cannot trigger events defined on InteractiveObject
on the skin. These include user interaction events such as mouseDown, mouseOut,
and click.
You can trigger events defined on UIComponent on
the skin, however. These events include creationComplete and
other lifecycle events.
A Button control’s default skin defines several Rect elements
and a Label control. These simple classes do not support any events.
To add lifecycle event listeners, you can wrap a simple element
or series of elements in a Group tag. This lets you register lifecycle
events that are defined on UIComponent.
The following example shows a Group inside the skin that triggers
a creationComplete event: