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:
[quickcode:noclick]
[/quickcode]
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.
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
It is a first flex most simple example I met (I didn’t find on purpose though). And you know, when I pressed ‘View Source’ in Safari it made me interested in Flex. This shows me one more time that JavaFX is to young for browser.
One thing there that kind of strange, why can’t I select text in your demo application?
And just to let you know. Something wrong with examples indentations: probably sometimes there is ‘tab’ symbol is used and sometimes it is ‘spaces’. Curly brackets in examples are put using different styles. Not a big deal – just to let you know.
Thank you, Mykola.
My take on JavaFx is that Flex IS JavaFx
I mean – let’s substitute “Java” with the “platform that makes most sense TODAY”. In the browser it’s undisputed Adobe Flash.
WBR,
Victor