The basic idea behind the Spark component model in Flex 4 is to entirely decouple the behavior of a component from its visual representation. You code the behavior of a component in one class and its visual representation in interchangeable MXML skin classes. This new architecture leads to lighter weight and easier to customize components.
As a simple example, I created a Spark version of the PhotoInput component I built for some of my Flex 3 samples (Salesbuilder and inSync). The PhotoInput component allows the user to take pictures using the webcam.
Here is the PhotoInput component with a minimalistic skin. Click the Start Camera button to start your webcam, and the Take Picture button to take a picture.
<components:PhotoInput skinClass="skins.PhotoInputSkin" width="200"/>
… and here is the same PhotoInput component with a different skin. Click the small webcam icon in the upper left of the component to start the webcam.
<components:PhotoInput skinClass="skins.PhotoInputSkin2" width="200"/>
As you can see in the main application class, this is the same PhotoInput component used with two different skins: PhotoInputSkin and PhotoInputSkin2.
PhotoInputSkin.mxml is defined as follows:
<?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("components.PhotoInput")]
</fx:Metadata>
<s:states>
<s:State name="capturing"/>
<s:State name="normal"/>
</s:states>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<mx:VideoDisplay id="videoDisplay" width="{hostComponent.width}" height="{hostComponent.width}"/>
<s:BitmapImage id="image" width="{hostComponent.width}" height="{hostComponent.width}"/>
<s:Button id="startCameraButton" label="Start Camera" width="100%" includeIn="normal"/>
<s:Button id="stopCameraButton" label="Stop Camera" width="100%" includeIn="capturing"/>
<s:Button id="takePictureButton" label="Take Picture" width="100%" includeIn="capturing"/>
</s:Skin>
… and PhotoInputSkin2.mxml is defined as follows:
<?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">
<fx:Script>
<![CDATA[
private function takePicture():void
{
// Show the picture that was just taken for 2 seconds,
// then show the webcam capture again
image.visible = true;
var myTimer:Timer = new Timer(2000, 1);
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
myTimer.start();
}
private function timerHandler(event:TimerEvent):void
{
if (currentState == "capturing") image.visible = false;
}
]]>
</fx:Script>
<fx:Declarations>
<mx:SoundEffect id="soundEffect" source="@Embed(source='assets/camera_click.mp3')" useDuration="false" />
</fx:Declarations>
<s:states>
<s:State name="capturing"/>
<s:State name="normal"/>
</s:states>
<!-- host component -->
<fx:Metadata>
[HostComponent("components.PhotoInput")]
</fx:Metadata>
<s:layout>
<s:BasicLayout/>
</s:layout>
<s:Rect radiusX="8" radiusY="8" top="0" bottom="0" left="0" right="0">
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="0x606060" alpha="1" ratio="0" />
<s:GradientEntry color="0x303030" alpha=".8" ratio="1" />
</s:LinearGradient>
</s:fill>
</s:Rect>
<s:Group top="4" bottom="4" left="4" right="4">
<s:layout>
<s:BasicLayout/>
</s:layout>
<mx:VideoDisplay id="videoDisplay" width="{hostComponent.width - 8}" height="{hostComponent.width}" includeIn="capturing"/>
<s:BitmapImage id="image" width="{hostComponent.width - 8}" height="{hostComponent.width}"
source="@Embed('assets/male_user_gray.png')" visible.capturing="false"/>
<s:Button id="startCameraButton" skinClass="skins.StartWebCamButtonSkin" toolTip="Start Camera"/>
<s:Button id="stopCameraButton" skinClass="skins.StopWebCamButtonSkin" includeIn="capturing" toolTip="Stop Camera"/>
<s:Button id="takePictureButton" skinClass="skins.TakePictureButtonSkin" label="Take Picture" width="100%" bottom="0" includeIn="capturing"
toolTip="Take Picture"
mouseDownEffect="{soundEffect}"
click="takePicture()"/>
</s:Group>
</s:Skin>
The PhotoInput class provides the basic behavior of the component (start and stop the camera, take the picture, etc.) and is defined as follows:
package components
{
import flash.display.BitmapData;
import flash.events.MouseEvent;
import flash.media.Camera;
import mx.controls.VideoDisplay;
import mx.graphics.ImageSnapshot;
import spark.components.Button;
import spark.components.supportClasses.SkinnableComponent;
import spark.primitives.BitmapImage;
[SkinState("capturing")]
public class PhotoInput extends SkinnableComponent
{
private var camera:Camera;
private var isCameraOn:Boolean = false;
[SkinPart(required="true")]
public var videoDisplay:VideoDisplay;
[SkinPart(required="true")]
public var image:BitmapImage;
[SkinPart(required="false")]
public var takePictureButton:Button;
[SkinPart(required="false")]
public var startCameraButton:Button;
[SkinPart(required="false")]
public var stopCameraButton:Button;
public function PhotoInput()
{
super();
}
override protected function partAdded(partName:String, instance:Object):void
{
super.partAdded(partName, instance);
if (instance == startCameraButton)
{
startCameraButton.addEventListener(MouseEvent.CLICK, startCameraButton_click);
}
if (instance == stopCameraButton)
{
stopCameraButton.addEventListener(MouseEvent.CLICK, stopCameraButton_click);
}
if (instance == takePictureButton)
{
takePictureButton.addEventListener(MouseEvent.CLICK, takePictureButton_click);
}
}
override protected function partRemoved(partName:String, instance:Object):void
{
super.partRemoved(partName, instance);
if (instance == startCameraButton)
{
startCameraButton.removeEventListener(MouseEvent.CLICK, startCameraButton_click);
}
if (instance == stopCameraButton)
{
stopCameraButton.removeEventListener(MouseEvent.CLICK, stopCameraButton_click);
}
if (instance == takePictureButton)
{
takePictureButton.removeEventListener(MouseEvent.CLICK, takePictureButton_click);
}
}
override protected function getCurrentSkinState():String
{
if (isCameraOn)
{
return "capturing";
}
return "normal";
}
private function startCamera():void
{
isCameraOn = true;
invalidateSkinState();
callLater(attachCamera);
}
private function attachCamera():void
{
camera = Camera.getCamera();
videoDisplay.attachCamera(camera);
}
private function stopCamera():void
{
isCameraOn = false;
invalidateSkinState();
videoDisplay.attachCamera(null);
}
private function takePicture():void
{
var bd:BitmapData = ImageSnapshot.captureBitmapData(videoDisplay);
image.source = bd;
}
private function startCameraButton_click(event:MouseEvent):void
{
startCamera();
}
private function stopCameraButton_click(event:MouseEvent):void
{
stopCamera();
}
private function takePictureButton_click(event:MouseEvent):void
{
takePicture();
}
}
}
Source Code
You can download the source code of the application here.
More Information
To learn more about skins, make sure you read Ryan Frishberg’s great article on DevNet.
6 Comments
nice work…thanks for the post.
Before I read this, I feel so confused about the skin. Now I think I’ve known some about the mystery of flex skin.
Learned some valuable info on creating skins in flex 4. Your post is very valuable! Thanks so much! :-)
Thank you for this article! It’s was very helpful to me.
Cool post dude!
I’m still trying to do the same, but I don’t know why is not working properly for me..
GOOOOD!! hehehhe
Great guide Christophe, normally people are code in Flex 4 as they did in Flex 3 and the purpose of the component model in Flex 4 is entirely different and I believe MUCH better.
Thanks for this guide, it will help a few understand how Flex 4 is supposed to be used.
3 Trackbacks
[...] Christophe Coenraets also written a super article Creating a Custom Component and Skins in Flex 4 [...]
[...] Christophe Coenraets Rich Internet Applications, Flex, AIR, Java Skip to content BioUsing Flex with Spring « Creating a Custom Component and Skins in Flex 4 [...]
[...] The Flex 4 learning curve will be from Spark and its new skin programming model. Here is another source to learn a few things from, Christonphoe Coenraets’s post Creating a Custom Component and Skins in Flex 4. [...]