Coming across several blogs that hint that custom Spark skins should not be created via OOP inheritance I felt challenged. After all, Spark skins are using inheritance already, so why should I be stripped of something I am so used to? In this post , I will create a PictureButton component that injects image into the Spark Button:
<PictureButton label="My PictureButton" pictureGroupName="statistic" skinClass="PictureButtonSkin" />
This is how the button looks
and you can run view-source enabled demo application if you, like me, do not have patience for the wordy blogs. PictureButton component features pictureGroupName property, which represents the common “group name” of three images for “up”, “over” and “down” states. Mangled with the “_up”, “_over” and “_down” suffixes it should yield the real image URLs (e.g. assets/statistic_up.png, assets/statistic_down/png etc.) :
Clearly, creating public class PictureButton extends spark.skins.sparkButton is a must, if anything – just to carry arround the pictureGroupName. Then we have three choices for the PictureButtonSkin:
a) create a brand new skin (this had been done before)
b) massage the clone of spark.skins.spark.ButtonSkin
c) build a descendant (in OOP terms) of the Spark skin.
This post is focusing on the last two approaches, particularly highlighting the OOP way.
Making Picture Button Skin By Cloning And Massaging Spark Button Skin
Massaging the original skin we redirect the HostComponent to point to our forthcoming PictureButton. Then, in the last, 8-th layer of original skin, we wrap the labelDisplay skin part with the VGroup and inject a bitmapImage. The “up”, “over” and “down” variants of the bitmapImage.source are bound to the bitmaps carried within the “hostComponent” :
<?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:fb="http://ns.adobe.com/flashbuilder/2009" minWidth="21" minHeight="21" alpha.disabled="0.5"
>
<fx:Metadata>
<![CDATA[
[HostComponent("PictureButton")]
]]>
<!-- The rest of the original spark.skins.spark.ButtonSkin - till layer 8 is omitted for brevity -->
. . . . .
<!-- layer 8: text -->
<!-- @copy spark.components.supportClasses.ButtonBase#labelDisplay -->
<!-- WE COMMENTED OUT ORIGINAL labelDisplay
<s:Label id="labelDisplay"
textAlign="center" verticalAlign="middle"
maxDisplayedLines="1"
horizontalCenter="0" verticalCenter="1"
left="10" right="10" top="2" bottom="2">
</s:Label>
-->
<s:VGroup horizontalAlign="center" verticalAlign="middle" left="3" right="3"
paddingTop="3" paddingBottom="3"
paddingLeft="3" paddingRight="3"
>
<s:BitmapImage id="bitmapImage"
source.up="{hostComponent.bitmapImageUp}"
source.over="{hostComponent.bitmapImageOver}"
source.down="{hostComponent.bitmapImageDown}"
/>
<s:Label id="labelDisplay" left="10" right="10" top="2" bottom="2"
textAlign="center" verticalAlign="middle"
maxDisplayedLines="1"
/>
</s:VGroup>
</s:SparkSkin> |
Now, let’s supplement this skin with the matching PictureButton code.
PictureButton Component
To make the very PictureButton we extend the Spark button and instantly create three SWFLoaders to facilitate load of three images:
public class PictureButton extends Button {
private var loaderUp:SWFLoader;
private var loaderDown:SWFLoader;
private var loaderOver:SWFLoader;
public function PictureButton()
{
super();
loaderUp = new SWFLoader;
loaderDown = new SWFLoader();
loaderOver = new SWFLoader();
loaderUp.addEventListener(Event.COMPLETE, onLoadComplete);
loaderDown.addEventListener(Event.COMPLETE, onLoadComplete);
loaderOver.addEventListener(Event.COMPLETE, onLoadComplete);
}
... |
Changing of the pictureGroupName will result in loading of the pictures, according to the setter below:
private var _pictureGroupName:String;
[Bindable]
public function set pictureGroupName (value:String):void {
if (_pictureGroupName !== value) {
_pictureGroupName = value;
loadPictures();
}
}
public function get pictureGroupName():String {
return _pictureGroupName;
}
private function loadPictures():void {
if (loaderUp) loaderUp.load("assets/" + pictureGroupName + ".png")
if (loaderDown) loaderDown.load("assets/" + pictureGroupName + "_down.png");
if (loaderOver) loaderOver.load("assets/" + pictureGroupName + "_over.png");
} |
Finally, as images get loaded we populate corresponding bindable bitmapImage properties that our custom skin so much depends on. Note, that being public these properties can be set from outside; you can avoid using pictureGroupName whatsoever and, in particular, you may load all your images from module classes or CSS styles etc.
[Bindable] public var bitmapImageUp:Object;
[Bindable] public var bitmapImageDown:Object;
[Bindable] public var bitmapImageOver:Object;
private function onLoadComplete(event:Event):void {
if (event.target == loaderUp) {
bitmapImageUp = event.target.content;
} else if (event.target == loaderDown) {
bitmapImageDown = event.target.content;
} else if (event.target == loaderOver) {
bitmapImageOver = event.target.content;
}
}
} |
OK. Both skin and control are done, but there is an afterthought: WHAT IF THE BASE SKIN CHANGES?
Obviously, we will be out of sync. Hence the need for OOP approach.
Making PictureButtonSkin WITH INHERITANCE
All we did in the cloned Skin was replacing original labelDisplay with the VGroup containing both labelDisplay AND bitmapImage.
We did it with the declarative MXML, however the same can be done in ActionScript:
package
{
import ...
public class PictureButtonSkin extends ButtonSkin {
protected override function childrenCreated():void {
super.removeElement(labelDisplay);
addElement(createVGroup());
super.childrenCreated();
}
[Bindable]
public var bitmapImage : spark.primitives.BitmapImage;
private function createVGroup():VGroup
{
var temp : spark.components.VGroup = new spark.components.VGroup();
temp.horizontalAlign = "center";
temp.verticalAlign = "middle";
temp.left = 3;
temp.right = 3;
temp.paddingTop = 3;
temp.paddingBottom = 3;
temp.paddingLeft = 3;
temp.paddingRight = 3;
temp.mxmlContent = [createBitmapImage(), createLabel()];
return temp;
}
private function createBitmapImage() : spark.primitives.BitmapImage
{
bitmapImage = new BitmapImage();
return bitmapImage;
}
private function createLabel() : spark.components.Label
{
labelDisplay = new Label();
labelDisplay.left = 10;
labelDisplay.right = 10;
labelDisplay.top = 2;
labelDisplay.bottom = 2;
labelDisplay.maxDisplayedLines = 1;
labelDisplay.setStyle("textAlign", "center");
labelDisplay.setStyle("verticalAlign", "middle");
labelDisplay.id = "labelDisplay";
return labelDisplay;
}
} |
Now, notice that we did not populate the source of the bitmapImage. The reason is that by diving into ActionScript we lost “luxury” of state-related code generation for “source.up”, “source.over” and “source.down”. To complement this we have to listen to “stateChanging” event and adjust the source ourselves:
public class PictureButtonSkin extends ButtonSkin
{
public function PictureButtonSkin(){
super();
addEventListener(StateChangeEvent.CURRENT_STATE_CHANGING, onStateChanging);
}
. . . .
private function onStateChanging(event:StateChangeEvent):void {
switch (event.newState){
case "down":
bitmapImage.source = hostComponent["bitmapImageDown"];
break;
case "over":
bitmapImage.source = hostComponent["bitmapImageOver"];
break;
case "default":
bitmapImage.source = hostComponent["bitmapImageUp"];
}
}
} |
And that is pretty much it.
Conclusions
Programmatic extension of skins is not that hard. As a developer, I prefer the OOP way to copy/paste.
I am well aware that it breaks the Designer/Developer separation of concerns as it is currently (I stress, currently) envisioned by Adobe.
I am well aware that using non-vector graphics in the first place, instead of Rect and Line primitives, has it’s own drawbacks.
Yet, being realistic, most often than not I will not have a designer to draw for me.
Neither do I get paid for drawing: I am a coder.
Why I like OOP? Less code.
Demo Application
SourceView
I’d like to use this opportunity and invite Flex developers living in Europe to attend our Advanced Flex Master Class in Brussels, Belgium on March 1 and 2, 2010.
Victor Rasputnis