Requirements
     
Prerequisite knowledge
Required products Sample files  

Basic familiarity with Flash Builder, MXML, and ActionScript is recommended.

Flash Builder (Download trial) spark_skin_parts.zip (52 KB)
 
User level      
All
     
 
One of the main advantages of Spark components in Flex 4 comes from an entirely different skinning mechanism, which offers Flex developers greater control over the look and feel of our applications. This tutorial will show you how to build skins that have their own visual components inside of them. Any component using these skins will have these parts built into them as well. To illustrate this, I will be explaining PowerWindow, a custom Panel component that utilizes these skin parts together with skin states and other skinning functionality to enable the user to resize, minimize, and maximize the component using buttons.
 

 
Using skins and skin parts

Spark skins are used to enhance the design and layout of any component to which they are applied. These skins are typically created in MXML and are used in Flex as classes. Prior to Flex 4, when using the MX components (also called Halo components), you had only simple skinning abilities: you could only wrap different graphics or compiled SWFs onto the component based on some skin styles predefined in the Application Programming Interface (API). The new Flex 4 skinning API enables a significant amount of customization, including:
 
  • Defining custom styles
  • Using images and pre-compiled SWF elements
  • Defining custom graphical elements using the Flash drawing API
  • Defining different states of your skin
  • Adding functional programming that references the parent component
  • Adding skin parts, or subcomponents
These additions to the Flex programming environment allow you to use skins to accomplish a great deal–essentially the skins can be responsible for all of the visual aspects of a component instance. In this tutorial, I will cover the last bullet in detail: using skin parts.
 
 
Built-in skin parts
Many of the components in the Spark framework have parts already built into them. You can find out what skin parts exist by visiting the Flex 4 API documentation. In the top bar for Spark objects supporting skins, there is a link labeled Skin Parts (see Figure 1).
 
The Skin Parts link in the Button API documentation.
Figure 1.The Skin Parts link in the Button API documentation.
 
When you click on this link, you will see the section of the API page that explains each of the skin parts available in the component or any object in its inheritance chain. Note that there is a skin part in the Button component called labelDisplay (inherited by the ButtonBase class) that is an instance of the TextBase class (see Figure 2). This is the part of the button used to display its label.
 
Skin parts for the Spark Button class.
Figure 2.Skin parts for the Spark Button class.
 
Types of skin parts
If you look closely at the skin part definition for the Spark Button class, you will see that it says the part type is static.
 
Every skin part is going to be either static, dynamic, or deferred. The skin part type is related to how it will be instantiated in the skin. In addition, each skin part might be required or not. A required skin part will need to exist whenever a skin is created for that component.
 
Static skin parts are instantiated along with the skin. They will always be accessible throughout the life of the skin and there can only be one instance of any static part. With the Button example, the labelDisplay skin part is static. You cannot create another skin part called labelDisplay, but you can access the labelDisplay part programmatically if you so choose (to change its text property, for example).
 
Dynamic skin parts are instantiated when needed, and many components do not have them. These are generally integral to the working of a particular component that the skin is related to. For example, the Spark ButtonBar component has three dynamic parts: firstButton, lastButton, and middleButton. These dynamic skin parts handle the skinning of each button in a button bar, but because each particular button may be styled differently depending on where it is in the ButtonBar instance the skins are applied dynamically at instantiation. For example, a ButtonBar with only two buttons would not need a middleButton skin part as it would only have a left and right button.
 
Dynamic skin parts are used in those components that have child elements instantiated at runtime. They will be instances of mx.core.IFactory, an interface class that is designed to create instances of other components, because the skin will create these new parts as needed by the application at runtime.
 
Deferred skin parts are instantiated based on some sort of trigger. These parts may not exist when the skin is created and applied to a component, and they are often not added until the user or the system interacts with the component.
 

 
Declaring and overriding skin parts

You’re now ready to get started on putting together the PowerWindow component and its related skin. With Flex 4, you can create a new component that handles specific functionality without having it coupled to the design. The design will be handled by the skin.
 
For this example, PowerWindowSkin.mxml will control the layout of the component. Download the sample file for this article, and import it in Flash Builder 4 by choosing File > Import Flex Project (FXP). The PowerWindowSkin.mxml file is in the com.adobe.examples.sparkskinparts package.
 
Note: I created this skin class file by right-clicking the PowerWindow component in Design view and selecting Create New Copy Of Skin. This created a new skin class based on the theme and component and linked them up.
 
To understand how Spark skins work, it helps to create a skin from scratch. To start, this component will be applied to the built-in Spark Panel component. This is done by declaring the host component using a metadata tag; for example:
 
<?xml version= 1.0 encoding= utf-8 ?> <s:SparkSkin xmlns:fx= http://ns.adobe.com/mxml/2009 xmlns:s= library://ns.adobe.com/flex/spark xmlns:mx= library://ns.adobe.com/flex/halo > <fx:Metadata> [HostComponent( spark.components.Panel )] </fx:Metadata> </s:SparkSkin>
The root tag of the MXML class here is SparkSkin. This defines the class as a Spark skin class, (supporting the Spark theme) and makes the skinning API available via its inheritance chain. After declaring the HostComponent metadata tag, you can interact with members of the host component’s class, including its built-in skin parts, by referring to the local hostComponent property. In the component, you can refer back to the skin using its local skin property. These properties enable a two way interaction between component and skin.
 
The Panel class has three built in skin parts (see Figure 3). The contentGroup skin part is where all of the child components will be stored and laid out. The controlBarGroup will handle layout of an optional control bar. The titleDisplay part is used to display whatever the user passes into the Panel’s title property. Note that these are all static; there will only be one instance of each and they will be created when the component is started up. They are not required, so you do not have to include these objects in your skin, however beware as some components will require skin parts in order to function properly.
 
The skin parts for the spark.components.Panel component.
Figure 3.The skin parts for the spark.components.Panel component.
 
Overriding skin parts
The next step is to understand how to override built-in parts in your skin. For example, you may want to override the way that titles are displayed in PowerWindow. Since the API documentation declared that a skin part called titleDisplay exists and is an instance of TextBase, then all you need to do is create an instance of this class (or any class that extends it) and set its id to titleDisplay; for example:
 
<s:Label id="titleDisplay" horizontalCenter="0" textAlign="center" />
The code above will override the default layout of the title and instead position the title in the center of the panel. You can also wrap it in a rectangle and add a gradient or background. You can do anything else you like to control how the title will display. The title of the host component will automatically display. If you wish, you could bind the text property of the object to another value; for example:
 
text="{hostComponent.labelProperty}"
As it stands now, this skin won’t display the contents of the host Panel because it is missing another skin part, contentGroup. Go ahead and add this now:
 
<s:Group x="0" y="30" id="contentGroup" />
This is about as simple an implementation as you can get, and you’ll probably want to add more to control the layout of the children, but you get the idea. So that’s the process: add in the required (and optional) components, set their properties, and lay everything out however you like. Just make sure to give these instances each an id that matches the skin part as defined in the API documentation.
 
 
Declaring new skin parts
If you wish to allow your custom components to have skinnable parts, you can declare them directly into your components. You do this using the SkinPart metadata in the script portion of your component. For example, the PowerWindow component allows users to skin the close and minimize buttons. The SkinPart metadata tag takes one parameter, required, which determines whether the following skin part will be required by the compiler. For example, here is the close button in the PowerWindow component:
 
[SkinPart(required="true")] public var closeIcon:Button;
This will create a skin part called closeIcon that you will implement later in the skin (in fact you will be required to) just as you did in the example above.
 

 
Using skin parts with skin states

Like components, skin parts can have states. Think about a button: it has its normal state (called up) when it isn’t being interacted with, but when you mouse over it, it may change appearance. It will also change appearance when you press the button down and possibly when the button is disabled. You can find these states for Spark components in the API documentation. Figure 4 shows the Button component’s skin states: disabled, down, over, and up. To display skin states for a component, click the Skin States link (see Figure 4).
 
Button skin states: diabled, down, over, and up.
Figure 4. Button skin states: diabled, down, over, and up.
 
Using these states in a skin, you can have different appearances within the same skin depending on the state of the skin itself. Like parts, states are defined both in the host component as well as in the skin.
 
In the component, declare the states using the SkinState metadata tags, passing each state’s name as the only parameter:
 
[SkinState("up")] [SkinState("over")] [SkinState("down")] [SkinState("disabled")]
In the skin, you use the states tag and populate it with an array of State objects, just as you would declare view states in a component. These states will need to be placed inside the skin for each state that is declared in the host component; for example:
 
<s>states> <s>State name="up" /> <s>State name="over" /> <s>State name="down" /> <s>State name="disabled" /> </s>states>
 
Relating parts to states
Once you have declared all of the states in your skin, you can use dot notation or the includeIn and excludeFrom properties to specify how your skin parts will behave when the skin state changes.
 
For example, to change the color of the button text when the button is in the down state, you might use the following code:
 
<s:Label id="labelElement" color="#000000" color.down="#D1D1D1" />
This can be done on the skin itself. In the button if you want to set the alpha to 0.5, you mark up the root tag of the skin element:
 
<s:SparkSkin xmlns:fx=http://ns.adobe.com/mxml/2009 xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled="0.5">
Any GraphicElement component you use in your skin (this includes textual components like Label), can use the includeIn and excludeFrom properties to specify what states that component should be used in. Adding a background when the button is in the over and down states (you can specify just one if you like), would require adding a new graphic element and then assigning it to that state using the includeIn property; for example:
 
<s:Rect height="100%" width="100%" includeIn="over,down"> <s:fill> <s:SolidColor color="blue" alpha=".5"/> </s:fill> </s:Rect>
The excludeFrom property works in the same way, only it has an opposite effect. It leaves the item out of the specified state or states.
 

 
Putting it all together in the PowerWindow component and skin

The PowerWindow custom skinnable component (PowerWindow.as) with its default skin (PowerWindowSkin.mxml) is shown in Figure 5.
 
The PowerWindow component maximized.
Figure 5.The PowerWindow component maximized.
 
The component, PowerWindow.as, implements the Spark Panel class and declares its events and functions. In order to implement the special skin and skinning functionality, the skin class wiill be declared in its skinClass property and all of the skin parts are declared in the class declaration using the SkinPart metadata tag. Note that only the close icon is required; this allows the skin to be used on windows that aren’t minimizable and aren’t resizable.
 
package com.adobe.examples.sparkskinparts { import spark.components.Panel; import mx.events.ResizeEvent; import spark.components.Button; public class PowerWindow extends Panel { // declare the skin parts - only close is required [SkinPart(required="true")] public var closeIcon:Button; [SkinPart(required="false")] public var minimizeIcon:Button; [SkinPart(required="false")] public var resizeGripper:Button; public function PowerWindow() { super(); } // add event listeners by overriding partAdded method override protected function partAdded(partName:String,instance:Object):void { // call super method super.partAdded(partName,instance); // now add listeners if ( instance == closeIcon ) { closeIcon.addEventListener(MouseEvent.CLICK, close); } if ( instance == resizeGripper ) { resizeGripper.addEventListener(MouseEvent.MOUSE_DOWN, resize); } } // dispatch close event (parent will handle) private function close(event:MouseEvent):void { this.dispatchEvent(new Event(Event.CLOSE)); } // dispatch resize event (parent will handle) private function resize(event:MouseEvent):void { var resizeEvent:ResizeEvent = new ResizeEvent(ResizeEvent.RESIZE,false,false,this.width,this.height); this.dispatchEvent(resizeEvent); } } }
You can see that each of the skin parts is accessible directly once added–they are public variables after all–so they can be used to add event listeners that handle whatever events that component can dispatch. Specifically, the close functionality will dispatch an event to allow the parent to handle the functionality, as removing the component will depend on how the component was instantiated by the parent (as a popup or in a component, for example). The same is done with resize, though you could probably add this functionality into the skin if you so choose (I have included links in the Where to go from here section that show how this can be done).
 
You’ll also notice that the minimize functionality is missing. That is because you are going to perform this from within the skin itself.
 
 
Relating parts to states
The skin is actually comprised of components that themselves are skinned. All of the buttons are skins comprised of BitmapImage primitives, and an alpha of 0.5 is applied for the disabled state as defined in the alpha.disabled property of each skin. While the whole process of creating these skins takes more time than using image objects directly, it offers a great deal of flexibility and power because each of these buttons has its own skin states built in, and anyone who wants to use this panel can just create new skins for these buttons to produce an entirely new look and feel. They don’t even need to re-skin the component itself if they don’t want to.
 
CloseButtonSkin.mxml
 
<?xml version="1.0" encoding="utf-8"?> <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled="0.5"> <fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata> <s:states> <s:State name="up" /> <s:State name="over" /> <s:State name="down" /> <s:State name="disabled" /> </s:states> <s:BitmapImage source="@Embed('assets/closeButton.png')" /> </s:SparkSkin>
The host component is declared in the metadata tag. This will match up the skin parts and make sure all required parts are present.
 
The PowerWindow skin includes all the required parts and all the optional parts as well. They are the buttons at the bottom of the PowerWindowSkin.mxml file. The skin declares the normal and minimized states as well as the required up, over, down, and disabled states. The minimizeIcon has an event listener in the MXML for its click event. When clicked, it will call the minimizeRestore function, which changes the height of the host component using the hostComponent scope, then sets the currentState property, which changes the state of the skin to either normal or minimize. Although you can build lots of functionality into Spark Skins, you need to be careful so you don’t make it more difficult for others to skin later.
 
The PowerWindow component minimized.
Figure 6.The PowerWindow component minimized.
 
Here is the code for the minimize button:
 
<s:Button id="minimizeIcon" right="{closeIcon.width + 10}" top="5" skinClass="com.adobe.examples.sparkskinparts.MinimizeButtonSkin" skinClass.minimized="com.adobe.examples.sparkskinparts.RestoreButtonSkin" click="minimizeRestore(event)"/>
The skinClass.minimized attribute forces a different skin (one that shows the restore button instead) to be used when in the minimized state.
 
The skin also it uses the excludeFrom property on the resizeGripper instance to remove it when the skin is in the minimized state.
 
PowerWindowSkin.mxml
 
<?xml version="1.0" encoding="utf-8"?> <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo"> <fx:Metadata> [HostComponent("com.adobe.examples.sparkskinparts.PowerWindow")] </fx:Metadata> <fx:Script> <![CDATA[ // how high to go when minimized private const HEADER_HEIGHT:int = 30; // height to return to when restoring private var originalHeight:int; // function to minimize/restore private function minimizeRestore(event:MouseEvent):void { if ( this.currentState == "normal" ) { originalHeight = hostComponent.height; hostComponent.height = HEADER_HEIGHT; this.currentState = "minimized"; } else { hostComponent.height = originalHeight; this.currentState = "normal" } } ]]> </fx:Script> <s:states> <s:State name="normal" /> <s:State name="minimized" /> <s:State name=”up” /> <s:State name=”over” /> <s:State name=”down” /> <s:State name=”disabled” /> </s:states> <!-- border --> <s:Rect height="100%" width="100%"> <s:stroke> <s:SolidColorStroke color="#000000" /> </s:stroke> </s:Rect> <!-- title bar background --> <s:Rect height="{HEADER_HEIGHT}" width="100%" top="0" left="0"> <s:fill> <s:SolidColor color="blue" alpha=".5"/> </s:fill> </s:Rect> <!-- title text --> <s:Label id="titleField" top="5" horizontalCenter="0" textAlign="center" fontWeight="bold" color="#000000" /> <!-- content group --> <s:Group x="0" y="30" id="contentGroup" /> <s:Button id="closeIcon" right="5" top="5" skinClass="com.adobe.examples.sparkskinparts.CloseButtonSkin"/> <s:Button id="minimizeIcon" right="{closeIcon.width + 10}" top="5" skinClass="com.adobe.examples.sparkskinparts.MinimizeButtonSkin" skinClass.minimized="com.adobe.examples.sparkskinparts.RestoreButtonSkin" click="minimizeRestore(event)"/> <s:Button id="resizeGripper" right="0" bottom="0" skinClass="com.adobe.examples.sparkskinparts.ResizeGripperSkin" excludeFrom="minimized" /> </s:SparkSkin>

 
Where to go from here

Now that you’ve seen how to use the Flex 4 framework’s new Spark skin parts to build a PowerWindow component and skin, you may want to check out the following resources to learn more about skinning, creating dynamic skin parts, and how to finish the PowerWindow component.