« March 2006 | Main | June 2006 »

April 24, 2006

Programmatic Skinning in Flex 2 Beta 2

This example application shows how to skin components using programmatic skins. These are skins which are written in ActionScript. The other type of skins, graphic skins, are images. You can combine the two skins as well as use them separately.

If you open the main application file, SkinningExample.mxml, you will see the <mx:Style> section. When you create skins you typically want all of the controls to have the same look. You may, for example, want only LinkButtons to have your special look, or, as in this example, you want many components to have your look.

You can run the application from this URL: http://www.keaura.com/skinning

This application works only with Flex 2 Beta 2 and the Flash Player 8.5 that goes with that release. The source is available from a right-click on the application once you have launched it. I used the application source packaging feature of Flex Builder 2.

Since I have created so many skins you might think to yourself that I have created a theme. This is fairly accurate, but I have not packaged these skins as such. I'll leave that for a future article.

Disclaimer

I am not an artist. I will not be offended if you think these skins are unattractive. The point is learn how to do skinning, not to make a MOMA piece. You can call this collection of skins my Orange Period.

Specifying Skins

We'll start with a top-down approach and look at how you specify the skins and relate them to the components. I've done this with styles, but you can also place these styles on individual component definitions - it just depends on what you want skinned.

This is the style definition for a Button:

Button {
	upSkin: 		ClassReference("skins.MyButtonSkin");
	overSkin: 		ClassReference("skins.MyButtonSkin");
	downSkin: 		ClassReference("skins.MyButtonSkin");
	disabledSkin: 	ClassReference("skins.MyButtonSkin");
}

The names of the skins are found in the Flex component API under the style section. The ClassReference style attribute names the ActionScript class which defines that skin. You can see that I have specified the same ActionScript class for each of the skins listed for the Button. When you see the code for the class it will be more obvious why I did it that way. Many times a skin for each state or part of a component share the same code and it often makes sense to use the same class. But you do not have to do that, you can write a separate class for every skin.

This is the code for the skins.MyButtonSkin class.

package skins {
	import mx.skins.ProgrammaticSkin;
	import flash.display.*;
	
	public class MyButtonSkin extends mx.skins.ProgrammaticSkin {
	
		private var _backgroundFillColor:uint;
		private var _lineThickness:int;
		
		public function MyButtonSkin()
		{
			_backgroundFillColor = 0xFFBB00;
			_lineThickness       = 1;
		}
	
		override protected function updateDisplayList( w:Number, h:Number ) : void
		{
			var g:Graphics = graphics;
			
			if( getStyle("lineThickness") ) {
				_lineThickness = getStyle("lineThickness");
			}
			if( getStyle("backgroundFillColor") ) {
				_backgroundFillColor = getStyle("backgroundFillColor");
			}
	
			switch( name ) {
				case "upSkin":
					break;
				case "overSkin": // make lighter
					_backgroundFillColor = 0xFFCC00;
					break;
				case "downSkin": // make darker
					_backgroundFillColor = 0xFFAA00;
					break;
				case "disabledSkin":
					_backgroundFillColor = 0x999999;
					break;
			}
			
			g.clear();
			
			// now draw the real button
			g.beginFill(_backgroundFillColor,1.0);
			g.lineStyle(_lineThickness,0x000000,0.4);
			g.moveTo(w,0);
			g.lineTo(10,0);
			g.curveTo(0,0,0,10);
			g.lineTo(0,h);
			g.lineTo(w-10,h);
			g.curveTo(w,h,w,h-10);
			g.lineTo(w,0);
			g.endFill();
		}	
	}
}

I have two class variables: _backgroundFillColor and _lineThickness which I initialize in the class constructor function.

Perhaps the most important piece of the skin is the updateDisplayList function. This function is declared as an override because it is replacing the function of the same signature in the base class, mx.skins.ProgrammaticSkin. It is in this function that the skin is drawn. Flex will call upon this function whenever it needs to have the skin drawn.

Within the updateDisplayList function you can see where the name of the skin is being tested. Look back to the Style section and you can see those names: "upSkin", "downSkin", and so forth. These names correspond to the case clauses in the switch statement. The only difference between the skins is the color. For "upSkin" and "downSkin" is tried to lighten and darken the color a bit. For "disabledSkin" I made it gray.

The button graphic is drawn using the drawing functions of the graphics property of the component. The graphics property does have some handy drawing functions, such as drawRect and drawCircle. You can read more about this class under flash.display.Graphics.

Another thing to notice in the updateDisplayList function is how the color and line style are set. While I did initialize these in the class constructor, I test the component's style for settings that would override the defaults.

The rest of the skins work in a similar fashion and you should have no problem adapting them. Just be sure to read all of the API information for the components you want to skin. Skins for scrollbars are often referenced via custom styles. One example is the Panel which you can see in the Style section.

Skinning in Flex 2 is fairly straightforward and once you get the hang of it you will be able to create innovative skins. Just let your imagination run wild.

Posted by pent at 10:21 PM

April 20, 2006

Updated Flickr PhotoSearch for Flex 2 Beta 2

It has been awhile since I last updated the Flickr PhotoSearch application. This new version, for Flex 2 Beta 2, is essentially the same as the previous version, but I changed the experience a little.

In the last version, clicking a thumbnail opened a dialog box with the enlarged image. In this version the gallery of thumbnails resizes to make room for the enlarged image. I also updated the search capabilities to include search by date. You will also have to click the Search button (magnifying glass) after clicking a row in the search history grid. I decided that you might want to add additional tags or other search options before launching the search. Of course once you take the source you can change it back if you wish.

I included with the Favorites panel a slide-show view. There is a small slide icon on the lower left corner of the Favorites panel. When you click it the Gallery turns into a slide viewer.

Once again I made heavy use of states and transitions. If you are having problems with them, take a look throughout the code to see working examples.

In the original Flex 1.5 version of this application I used a Tile with Repeater to build the Gallery and Favorites panels. In the first Flex 2 Alpha 1 version of this application I switched to using a TileList and a renderer. For this version I have switched back to the Tile and Repeater. I found that since I am only grabbing enough images to fill the screen (and not all the images), the performace of the Tile and Repeater were better than the TileList. This was especially true when using a Resize effect during the transition from the full Gallery state to the enlarged image state.

Finally, if you are unsure of what something does, hover your mouse over it and a toolTip should appear.

The source and zip file are now available directly from the program. I used Flex Builder 2's source code packaging tool. Once the application has started, right click and pick View Source.

Launch the application from this URL: http://www.keaura.com/flex2b2flickr/ from a browser with the Flash Player 8.5 from the Flex 2 Beta 2 site at Adobe Labs

I will try to keep this application more up-to-date as Flex 2 evolves.

This application is compatible only with Flex 2 Beta 2 and the Flash Player 8.5 that accompanies that release. If you have any other version of Flex 2 you will need to recompile (and possibly change) the source code to work with your Flash Player 8.5

Posted by pent at 10:02 PM

April 14, 2006

A List itemRenderer using States and Transitions

Here's a little demo of how to expand a list item in place. This demo uses CheckBoxes for the itemRenderer, but when you click a box, the list item grows larger revealing more data.

The itemRenderer is actually a Canvas with a CheckBox for a child. The itemRenderer uses states. The initial state contains this CheckBox. The "expandedState" contains a List. A transition is used to hide and show the list by using a Resize effect.

The List must have variableRowHeight set to true, otherwise this won't work.

Compatable with Flex 2 Beta 2 Public release. Bear in mind that Flex 2 is in beta and changes will occur before the final release.

Main Application

Let's start with the main application which I call "ExpandoList.mxml":

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" layout="absolute">
    <mx:Script>
    <![CDATA[
        import mx.collections.ArrayCollection;
        // This collection shows just the title in the list as a CheckBox. When
        // one is checked, it expands to review the models array.
        [Bindable]
        public var dp:ArrayCollection = new ArrayCollection(
            [ {title:"Ford", models:["Fusion","Taurus","Mustang"]},
              {title:"Volkswagen", models:["Passat","Jetta","Beetle", "Golf", "GTI"]},
              {title:"Infiniti",models:["FX35","GX35","Q45","M35"]},
              {title:"Audi",models:["A3","A4","A6"]}
            ]);
    ]]>
    </mx:Script>    

    <!-- Simple list with 2 important points:
         1. variableRowHeight = "true" allows for rows to be of differing height
         2. itemRenderer = "DetailItem" specifies the DetailItem.mxml as the renderer
     -->
    <mx:List dataProvider="{dp}"  width="270" height="315" 
        horizontalCenter="0" 
        verticalCenter="0" 
        columnCount="1"
        columnWidth="250"
        variableRowHeight="true"
        itemRenderer="DetailItem"
        selectionColor="0xFFFFFF"/>
</mx:Application>

The Script block just defines the ArrayCollection. Assume this data is the result of a data services call.

The List is defined with this ArrayCollection as its dataProvider and variableRowHeight is set to true. The itemRenderer is set to the class name "DetailItem".

The itemRenderer

The DetailItem component is shown next. I've broken the code into parts for readability. You can combine them in the order given to get working code.

<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" width="201" height="36">

    <!-- This is the base state and it just defines the simple checkbox
      -->
    <mx:CheckBox x="10" y="10" label="{data.title}" width="181"
        click="changeState(event)"/>

The component extends Canvas. This is a good choice because Canvas does not impose any restrictions on the layout of its children. When you make an itemRenderer, consider how much effort you want to put into positioning the items. For example, if you want all of the components in the renderer on one line, use an HBox. Since this component is going to change size, Canvas is a perfect choice.

A single alternate state (from the default base state) is defined, "expandedState". You can see that there are two elements. SetProperty changes this component's (Canvas) height. The second adds a List with the ac member as its dataProvider.

    <mx:states>
        <mx:State name="expandedState">
            <mx:SetProperty name="height" value="123"/>
            <mx:AddChild position="lastChild">
                <mx:List left="10" y="36" right="10" dataProvider="{ac}" height="77"></mx:List>
            </mx:AddChild>
        </mx:State>
    </mx:states>

The Script block defines the ArrayCollection and an event handler for the CheckBox (see above). When the box is selected (checked), the currentState is changed to "expandedState", triggering the items in the states above.

    <mx:Script>
    <![CDATA[
        import mx.collections.ArrayCollection;
        [Bindable]
        public var ac:ArrayCollection;
  
        private function changeState(event:flash.events.Event) : void
        {
            if( event.target.selected ) 
                currentState = "expandedState";
            else
                currentState = "";
        }

        // the setting of the data property is overridden to create the
        // ArrayCollection from the Array of models listed in the dataProvider
        // this this List.
        override public function set data(value:Object) : void
        {
            super.data = value;
            ac = new ArrayCollection(value.models);
        }
    ]]>
    </mx:Script>

The transition defines how the state changes take place. Transitions are optional and apply effects to the change. You can really see what this transition does by first running this example WITHOUT the transition. All this transition does is provide a better visual experience.

When you run this example without the transition, clicking the CheckBox just makes the canvas bigger with the List of models.

Now run the example using this transition. When you click the CheckBox the list now opens smoothly, revealing the models. Click the CheckBox again and the item glides back up.

The fromState and toState are both *, wildcards, meaning that this transition applies when going from any state to any other state. The transition is just a Resize effect on the entire component (Canvas). What's interesting about this is that Flex will figure out what the before and after sizes are for the Resize effect without needing to specify that in the Resize tag.

    <mx:transitions>
        <mx:Transition fromState="*" toState="*">
            <mx:Resize target="{this}" />
        </mx:Transition>
    </mx:transitions>

Finally, end the component with the matching Canvas tag.

</mx:Canvas>

Quick Summary

You can use states and transitions pretty much anywhere, such as in an itemRenderer. Transitions make the user experience better and often do not take much coding. States and transitions can become quite complex. I suggest that you start off with something simple, such as this example, to get familiar with them. Perhaps add another state to this example and use a different transition.

Posted by pent at 08:31 PM