Creating a Custom Component and Skins in Flex 4

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.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DZone
  • LinkedIn
  • StumbleUpon
  • Twitter
This entry was posted in Flex. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

2 Comments

  1. Posted January 23, 2010 at 10:33 pm | Permalink

    nice work…thanks for the post.

  2. Posted February 4, 2010 at 6:20 pm | Permalink

    Muito bom!!!

3 Trackbacks

  1. By Spark Component Model 101 Links - One Hungry Mind on January 23, 2010 at 7:54 pm

    [...] Christophe Coenraets also written a super article Creating a Custom Component and Skins in Flex 4 [...]

  2. By Cool ItemRenderers Made Easy in Flex 4 on January 28, 2010 at 10:37 am

    [...] Christophe Coenraets Rich Internet Applications, Flex, AIR, Java Skip to content BioUsing Flex with Spring « Creating a Custom Component and Skins in Flex 4 [...]

  3. By Good example for learning Flex 4 Skining on January 28, 2010 at 11:24 pm

    [...] 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. [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>