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.

  • Pingback: Spark Component Model 101 Links - One Hungry Mind()

  • nice work…thanks for the post.

  • Pingback: Cool ItemRenderers Made Easy in Flex 4()

  • Pingback: Good example for learning Flex 4 Skining()

  • Yucheng

    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.

  • Bob

    Great post. I have been looking for something like this for the last couple days.

  • victoria

    This was really helpful. I tried to switch over the mx:VideoDisplay to use Spark VideoDisplay, but there doesn’t appear to be a spark equivalent of attachCamera. Do you know what I should use instead?

  • Arthur Clifford

    I don’t know if there’s any reason not to do this, but you can avoid having to do your AddPart and RemovePart overrides by setting the click handlers for your buttons in the mxml to use hostComponent.FunctionName()

    so:


    could become

  • Arthur Clifford

    Sorry, my examples got cut, basically just add click=”hostComponent.startCamera()” to whatever button in any skin you want to perform the start camera option. If you do that and you aren’t referring tot he start camera button in any other functionality you don’t have to declare it a skin part or include it in the add parts override.

  • Gil

    Wow, it sure surprised me! Nice post- I’ll use it.

  • rama

    Thank you for this article…It’s was very helpful to me.

  • Great guide Christophe. I am familiar with Flex 3 and am trying out your example to see if I can get some of my components working properly with Flex 4 the same way. Great example, thanks again!

  • Hi, nice post, thank you…i wrote something similiar last week (but in spanish) and also a example of creating a skin with Adobe Catalyst. Here at the company i work for are thinking of moving from Flex3 to Flex4. Its a huge project so its going to take a while probably, but i think it will pay…

  • anon

    thx

  • YASIN

    im from iran
    i wanted how can make a skin in visuall basic
    sorry if u know send to this email plz
    if know sombody call to this email
    thanks for ur website
    bye

  • YASIN
  • good example.

  • fdsf

    when change the datagrid column

  • Thanks

  • Thanks to my father who informed me on the topic
    of this web site, this blog is in fact awesome.

css.php