The first code snippet is the definition of the
FancyComponent
object, which is an extension of the Flex SkinnableContainer
. There are three SkinParts
defined, all of which are required to render this component. The skin parts are s:Image
, s:TextArea
, and s:Button
. Pay attention to the names of these variables, as they will be important later on. The
FancyComponent
overrides a function of the parent called partAdded
. This is the principal function responsible for adding skin parts to this container. After calling the parent implementation, notice that I use if-else logic to see if the incoming skin part is one of the parts I have defined. Also notice that if it is a part that I have defined, that I am doing something to it: for the image
I am setting its source, for the textArea
I am setting its parameter textFlow
, and for the closeButton
I am setting label and adding a MouseEvent
listener to it. The class has public variables for setting those values set in the partAdded
function (imgSrc
, msgText
, and closeButtonLabel
respectively).The Fancy Component
package com.charles.components
{
import flash.events.Event;
import flash.events.MouseEvent;
import flashx.textLayout.elements.TextFlow;
import mx.effects.IEffectInstance;
import mx.events.FlexEvent;
import spark.components.Button;
import spark.components.Image;
import spark.components.SkinnableContainer;
import spark.components.TextArea;
import spark.effects.Fade;
[Event(name="closeMe", type="flash.events.Event")]
public class FancyComponent extends SkinnableContainer
{
[SkinPart(required="true")]
public var image:Image;
[SkinPart(required="true")]
public var textArea:TextArea;
[SkinPart(required="true")]
public var closeButton:Button;
public var imgSrc:Object;
public var msgText:TextFlow;
public var closeButtonText:String;
protected var fadeEffect:Fade;
public function FancyComponent()
{
addEventListener(FlexEvent.CREATION_COMPLETE, addFadeEffect);
super();
}
override public function effectFinished(effectInst:IEffectInstance):void{
if(effectInst.effect == fadeEffect){
closeButton.removeEventListener(MouseEvent.CLICK, removeView);
dispatchEvent(new Event("closeMe"));
}
super.effectFinished(effectInst);
}
override protected function partAdded(partName:String, instance:Object):void{
super.partAdded(partName, instance);
if(instance == textArea){
textArea.textFlow = msgText;
} else if(instance == image){
image.source = imgSrc;
} else if(instance == closeButton){
closeButton.label = closeButtonText;
closeButton.addEventListener(MouseEvent.CLICK, removeView);
}
}
protected function addFadeEffect(event:FlexEvent):void{
removeEventListener(FlexEvent.CREATION_COMPLETE, addFadeEffect);
fadeEffect = new Fade();
fadeEffect.alphaFrom = 1;
fadeEffect.alphaTo = 0;
fadeEffect.duration = 1000;
}
protected function removeView(event:MouseEvent):void{
removeFancyComponent();
}
protected function removeFancyComponent():void{
fadeEffect.stop();
fadeEffect.play([this]);
}
}
}
Skinning isn't the only thing occurring the
FancyComponent
. In the constructor, I added a FlexEvent
to this
object to listen for the creationComplete
event. When the event fires, this
object will remove the event and set up a fade effect on the object. When the closeButton
is clicked, the whole container will begin to fade away. Overriden is the effectFinished
function, with special handling to remove the closeButton
's event listener and to dispatch an event called closeMe
. The event is defined as metadata above the class declaration; this allows implementing objects to explicitly assign functions (like a button's click parameter when defining it in mxml).
With the underlying component defined, skins are now created. Below are two different skins. Notice that there are common features to each of the skins:
- Each component contains a spark group with id
contentGroup
. - Do you remember the skin part variable names? Within the
contentGroup
, I haveimage
,textArea
, andcloseButton
as ids for their respective components in theFancyComponent
object (s:Image
,s:TextArea
,s:Button
). When the skin is rendered,partAdded
inFancyComponent
will be called and each of these mxml components will have their appropriate attributes set.
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("spark.components.SkinnableContainer")]
</fx:Metadata>
<!-- states -->
<s:states>
<s:State name="disabled" />
<s:State name="normal" />
</s:states>
<s:Rect left="0" right="0" top="0" bottom="0"
radiusX="10" radiusY="10" width="100%" height="100%">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry alpha="1"
color="#B0B0B0"
color.disabled="#E0E0E0"/>
<s:GradientEntry
color="#E8E8E8"
color.disabled="#E0E0E0"
alpha=".15"
/>
</s:LinearGradient>
</s:fill>
</s:Rect>
<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->
<s:Group id="contentGroup">
<s:layout>
<s:HorizontalLayout
paddingBottom="12"
paddingLeft="12"
paddingRight="12"
paddingTop="12"
/>
</s:layout>
<s:Image id="image" width="75" height="75"/>
<s:VGroup width="100%" height="100%">
<s:TextArea id="textArea" height="100%" width="100%"/>
<s:HGroup width="100%">
<s:Spacer width="100%"/>
<s:Button id="closeButton"/>
</s:HGroup>
</s:VGroup>
</s:Group>
</s:Skin>
AlternativeModernSkin
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<!-- host component -->
<fx:Metadata>
[HostComponent("spark.components.SkinnableContainer")]
</fx:Metadata>
<!-- states -->
<s:states>
<s:State name="disabled" />
<s:State name="normal" />
</s:states>
<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->
<s:Rect left="0" right="0" top="0" bottom="0"
radiusX="10" radiusY="10" width="100%" height="100%">
<s:fill>
<s:SolidColor color="#C80000"/>
</s:fill>
</s:Rect>
<!-- SkinParts
name=contentGroup, type=spark.components.Group, required=false
-->
<s:Group id="contentGroup">
<s:layout>
<s:HorizontalLayout
paddingBottom="12"
paddingLeft="12"
paddingRight="12"
paddingTop="12"
/>
</s:layout>
<s:VGroup height="100%">
<s:Image id="image" width="75" height="75"/>
<s:Spacer height="100%"/>
<s:Button id="closeButton"/>
</s:VGroup>
<s:TextArea id="textArea" height="100%" width="100%"/>
</s:Group>
</s:Skin>
Notice that the skins are different, and that they are simple and free of any implementation details. The implementation details are handled by the
FancyComponent
. Therefore, the developer can use this same FancyComponent
in different ways by laying out differently the image
, textArea
, and closeButton components
. The last step is to implement the
FancyComponent
. Below is mxml that does this. Since we have defined required SkinParts
in FancyComponent
, the MINIMUM requirement for any skin assigned to the FancyComponent
would be those components. Also, the FancyComponent
cannot be rendered without a skin that implements those SkinParts
.
Implementation
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication 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:components="com.charles.components.*"
backgroundColor="#000000">
<fx:Script>
<![CDATA[
import com.charles.assets.Assets;
import mx.controls.Alert;
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<components:FancyComponent width="300"
height="300"
imgSrc="{Assets.exclaim}"
closeButtonText="Close Me!"
skinClass="com.charles.skins.ModernSkin">
<components:msgText>
<s:TextFlow>
<s:p>
This is a <s:span fontWeight="bold">
fancy
</s:span> component. What is rather interesting is that thanks to the underlying
class and skin class, you don't really care about anything but putting in the
important information you desire.
</s:p>
</s:TextFlow>
</components:msgText>
</components:FancyComponent>
<components:FancyComponent width="300"
height="210"
imgSrc="{Assets.exclaim}"
closeButtonText="Close Me!"
skinClass="com.charles.skins.AlternativeModernSkin"
closeMe="{Alert.show('Notice this!', 'listening for closeMe');}">
<components:msgText>
<s:TextFlow>
<s:p>
This is a <s:span fontWeight="bold">
fancy
</s:span> component. What is rather interesting is that thanks to the underlying
class and skin class, you don't really care about anything but putting in the
important information you desire.
</s:p>
</s:TextFlow>
</components:msgText>
</components:FancyComponent>
</s:WindowedApplication>
This is only a small taste of what clever component development and skinning can do. If you find this appealing, you might want to experiment with
SkinParts
that aren't required ([SkinPart(required="false")]
). This means that a single component can be multi-purposed both visually and functionally (for example, you can have a three buttoned component that optionally implements two of the buttons, therefore fade effects and other features such as text areas need be applied once, and the partAdded
method will handle the cases where the non-required skin parts are present).
No comments:
Post a Comment