Programmatic Button Skins in Flex 3

The Flex CookBook recipe call prompted me to pull together some more generic, but useful code samples, so I figured I’d post them here and keep my fingers crossed that a recipe or two slip into the book unnoticed 🙂

I’ll explain a little recipe I use for programmatic skinning on buttons in particular on some projects. If you need fast, extendable, easy on the bandwidth buttons then this one is highly useful. To start off, I picked buttons because they’re the most generic examples of how to use programmatic skin, and probably the most likely target for clean, simple skins. In the future, I’ll post more skins for other components.

Complete source files:

Programmatic Button Skins – source files

The Code:
I’ll keep this post short and simple, there’s no need for lengthy instructions, but I know a lot of folks out there probably aren’t even aware that buttons can be skinned programatically, as well as any component. So put on your apron and get your spatula, we’ll get a little messy with this first recipe.

I’ll start off with the MXML and the actual buttons. You’ll notice that there’s absolutely nothing tough about this code, you can use any button anywhere and just make sure to set it’s styleName attribute to the CSS class that contains the skin you want to use:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:Script>
		<![CDATA[
			import mx.events.SliderEvent;
			import utils.DefaultValues;
			import utils.ImageResizer;

			public function doChangeSize(event:SliderEvent):void{
				// get the value of the hSlider, round it and divide by 100 for consistency
				var multiplier:Number = Math.round(hSlider.value) / 100;
				//trace(multiplier);
				// new height, width, x, and y for image
				img.width = DefaultValues.maxImageWidth * multiplier;
				img.height = DefaultValues.maxImageHeight * multiplier;
				img.x = ImageResizer.calcX(container.x,container.width,img.width);
				img.y = ImageResizer.calcY(container.y,container.height,img.height);

				// to avoid a "jump in position", find out where the full image's x,y coords are when the slider is
				// at 100% and place the image at those coords within it's parent container
				trace(img.x + " " + img.y);
			}
		]]>
	</mx:Script>

	<mx:Canvas width="500" horizontalScrollPolicy="off" verticalScrollPolicy="off" height="500" id="container">
		<mx:Image id="img" source="@Embed('assets/images/pxl_white_logo.jpg')" x="161" y="199"/>
		<mx:HSlider id="hSlider"
			minimum="0" maximum="100"
			tickColor="black"
			value="100"
			liveDragging="true"
			thumbDrag="doChangeSize(event)" width="339" x="80" y="335"
			showTrackHighlight="true"
			themeColor="#ff8c00"/>
	</mx:Canvas>
</mx:Application>

The only things to be aware of in your MXML are the reference to the external style sheet and setting your styleNames on the buttons themselves.

Add a little CSS for flavoring:

In the zip file, you’ll see all the other classes, but here I’ll point out the GradientRectangleSkin CSS selector. In the directory structure laid out in this example, we have our MXML reference the css/styles.css stylesheet which contains 3 class selectors. For this example, we can see the .gradientRectangleSkin class below. We just set some pretty standard styles on our button, and just make sure to reference the actual classes that will become the programmatic skins for our button “states” (upSkin, downSkin, overSkin, disabledSkin).


.gradientRectangleSkin {

    fontFamily: Arial;
    fontSize: 12;
    color: #FFFFFF;
    textAlign:left;
    width:150;
    height:30;

upSkin:ClassReference('programmaticSkinClasses.GradientRectangleSkin');
downSkin:ClassReference('programmaticSkinClasses.GradientRectangleSkin');
overSkin:ClassReference('programmaticSkinClasses.GradientRectangleSkin');
disabledSkin:ClassReference('programmaticSkinClasses.GradientRectangleSkin');
}

Yumm, let that CSS simmer for a while:

Next comes the fun part, where we actually make things happen. The programmatic skin classes contain all the logic we’ll need to draw out our actual skin and add some spice to our buttons. Since we’re using the GradientRectangleSkin, we have a little more going on here than just the run-of-the-mill flat colored button.

To give our button a little pazazz, we have our class create a few things:

  • A Gradient Background
  • Different gradients for button states
  • A drop shadow to appear on every state except downSkin

The reason for leaving off the shadow on the downSkin state is to give the appearance of the button being depressed when the user clicks. In our directory structure, our skin classes are stored in the programmaticSkinClasses folder, and this one is called GradientRectangleSkin.as. So here’s what the class looks like:


package programmaticSkinClasses {

	import mx.core.UIComponent;
	import flash.filters.DropShadowFilter;

	public class GradientRectangleSkin extends UIComponent	{

			import flash.display.Graphics;
			import flash.geom.Rectangle;
			import mx.graphics.GradientEntry;
			import mx.graphics.LinearGradient;

			protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
			   super.updateDisplayList(unscaledWidth,unscaledHeight);
				var w:Number = unscaledWidth;
				var h:Number = unscaledHeight;

				// hold the values of the gradients depending on button state
				var backgroundFillColor:Number;
				var backgroundFillColor2:Number;

				var fill:LinearGradient = new LinearGradient();

				// reference the graphics object of this skin class
				var g:Graphics = graphics;
				g.clear();

				//	which skin is the button currently looking for? Which skin to use?
				switch (name) {
					case "upSkin":
						backgroundFillColor = 0x929292;
						backgroundFillColor2 = 0x000000;
						break;
					case "overSkin":
						backgroundFillColor = 0x696969;
						backgroundFillColor2 = 0x504F4F;
						break;
					case "downSkin":
						backgroundFillColor = 0x888888;
						backgroundFillColor2 = 0x777777;
						color: 0xFF0000;
						break;
					case "disabledSkin":
						backgroundFillColor = 0xCCCCCC;
						backgroundFillColor2 = 0xCCCCCC;
						break;
				}
				// depending on which state the button's in, we set our color for the
				// gradients on the skin
				var g1:GradientEntry = new GradientEntry(backgroundFillColor,.10,100);
				var g2:GradientEntry = new GradientEntry(backgroundFillColor2,.60,100);

				fill.entries = [g1,g2];
				fill.angle = 90;
				// fill the rectangle
				g.moveTo(0,0);
				fill.begin(g,new Rectangle(0,0,w,h));
				g.lineTo(w,0);
				g.lineTo(w,h);
				g.lineTo(0,h);
				g.lineTo(0,0);
				fill.end(g);
				// if we're not showing the down skin, show the shadow. Otherwise hide it on the "down state" to look like it's being pressed
				if(name != "downSkin") {
		                     filters = [new DropShadowFilter(4, 45,0x000000,.2)];
				}
			 }
	}
}

In this class, the first thing we’re doing is extending UIComponent and overriding it’s updateDisplayList() function. At the top, we set 2 empty vars for background colors that will hold our values for this gradient and we create a new LinearGradient to use as our fill.

We make a reference to the UIComponent’s graphics object, and clear it to make sure we don’t get any surprises between button states. Now, we’re going to use a switch statement on the buttons current “state name”. We need to know which “state” is being requested depending on the user’s interactivity.

We’re going to switch on the button’s current “state” to figure out which gradient colors to draw out. If we need the upSkin, we’re going to set the background colors to a nice gray/black gradient look by setting:


case "upSkin":
	backgroundFillColor = 0x929292;
	backgroundFillColor2 = 0x000000;
	break;

We then break out of the switch and set our actual GradientEntries:

</pre>
<pre lang="actionscript">var g1:GradientEntry = new GradientEntry(backgroundFillColor,.10,100);
var g2:GradientEntry = new GradientEntry(backgroundFillColor2,.60,100);

The .10 and .60 are to set the location on the skin where that gradient color will actually begin. This combination makes a nice smooth transition for our recipe. Now we know which state we’re going to show and which gradient colors for that state, let’s draw it out with a little butter. Nah, forget the butter, just the code is fine:


fill.entries = [g1,g2];
	fill.angle = 90;
	// fill the rectangle
	g.moveTo(0,0);
	fill.begin(g,new Rectangle(0,0,w,h));
	g.lineTo(w,0);
	g.lineTo(w,h);
	g.lineTo(0,h);
	g.lineTo(0,0);
	fill.end(g);

We’ve just told the Flex compiler to use our gradient colors for the fill, to begin to draw a new Rectangle class using our unscaled height and width, fill it with our gradients and finish up by cleaning any left over paint off the floor.

Add a dropshadow for some spice:

Another little addition I like to use in my recipe is to add a drop shadow to each state except the downSkin. If we’re currently using the downSkin state, the user is pressing the button so we’ll remove the drop shadow to make the button appear to be pressed into the background.


// if we're not showing the down skin, show the shadow. Otherwise hide it on the "down state" to look like it's being pressed
if(name != "downSkin") {
     filters = [new DropShadowFilter(4, 45,0x000000,.2)];
}

That is one fine looking button:

Winning awards for graphic design merit? Perhaps not, but by using the programmatic skins, you’re open to extending many possibilites, and since they’re vector graphics being drawn on the fly with Flex’s powerful graphics classes, you’re saving a lot of download time. You just put your app on a diet and it’s looking and feeling a lot better.

This recipe was short and sweet, but in the near future I’ll add some more programmatic skinning with other cool ideas you can use to create a nice looking interface that’s easy on the startup time.

Facebooktwitterlinkedin

14 Comments

  1. Gareth Edwards

    I have also started doing some programmatic skinning.

    Is there a wordpress plugin you are using for you snippets of code?

    Cheers
    Gareth.

    Gareth Edwards’s last blog post..CFCAMP AU – Ben Forta is coming to Brisbane.

  2. Dave (Post author)

    Gareth,
    I use the wp-syntax plugin for WordPress, which works great. http://wordpress.org/extend/plugins/wp-syntax/page/2#post-1246

    There’s no built in MXML highlighter, so I found this one on defusion.org http://www.defusion.org.uk/archives/2007/10/04/code-mxml-geshi-language-file/

    It’s pretty decent, though it will change the case sensitivity of the mxml tags, you have to make sure they stay the way you want them if you edit your post.

    Enjoy!

    Dave

  3. Jake

    Hi Dave!

    Thanks so much for the post. Very helpful! I have a follow up question:

    Is it possible to draw a shape on top of a programmatic button? What I’d like to do is add a triangle to a programmatic button so that if the toggle state is set to “true” it would look like an accordion button. Ideally, I’d like to see the triangle redrawn for the selected state of the button so that it is pointing down rather than to the side.

    Is something like this possible? Or do I need to stick with a .png or .swc skinning technique for this?

  4. Dave (Post author)

    Hey Jake,
    No problem. I believe it’s totally possible. Here’s some fancy triangle code http://livedocs.adobe.com/flex/3/html/help.html?content=Drawing_Vector_Graphics_4.html

    I’d make sure to addChild() on top of the gradient background so that it shows up on screen. Give that a try and let me know how it goes.

  5. Jake

    Hey Dave,

    Thanks so much for the shove in the right direction. It did, in fact, work! I really appreciate it.

  6. Dave (Post author)

    Hey Jake,
    Awesome, glad to hear it! I’d be interested to see how it fits together if you want to post the code. Might be other people who would be interested to see it work too.

    Good work man, thanks for getting back to me!

  7. Jake

    Hey Dave,

    What I’m posting was placed at the bottom of the code you’ve provided here on this page. The code draws two different triangles (one for each selected state of a toggle button) and then hides both of them. It then uses another switch statement to show and hide them based on which button skin is being asked for.

    I have a sneaking suspicion that there is a more elegant way to do this . . . and the one issue I’m still having is that if the button ever gets redrawn because of parent container widths, I end up with two triangles side by side. Very strange. But this only happens with percentage based button widths. If you have a fixed width, this is a non-issue.

    Here’s the code:

    var triangleHeight:uint = 8;
    var triangle:Shape = new Shape();
    var triangle2:Shape = new Shape();

    triangle.graphics.beginFill(0xFFFFFF);
    triangle.graphics.moveTo((unscaledWidth – 18), 4);
    triangle.graphics.lineTo((unscaledWidth – 18) + triangleHeight/2, triangleHeight);
    triangle.graphics.lineTo((unscaledWidth – 18), triangleHeight + 4);
    triangle.graphics.lineTo((unscaledWidth – 18), 4);
    this.addChild(triangle);
    triangle.visible = false;

    triangle2.graphics.beginFill(0xFFFFFF);
    triangle2.graphics.moveTo((unscaledWidth – 20), triangleHeight);
    triangle2.graphics.lineTo((unscaledWidth – 20) + triangleHeight, triangleHeight);
    triangle2.graphics.lineTo((unscaledWidth – 20) + 4, triangleHeight + 4);
    triangle2.graphics.lineTo((unscaledWidth – 20), triangleHeight);
    this.addChild(triangle2);
    triangle2.visible = false;

    switch(name) {
    case “upSkin”:
    triangle2.visible = false;
    triangle.visible = true;
    break;
    case “overSkin”:
    triangle2.visible = false;
    triangle.visible = true;
    break;
    case “downSkin”:
    triangle2.visible = false;
    triangle.visible = true;
    break;
    case “selectedUpSkin”:
    triangle.visible = false;
    triangle2.visible = true;
    break;
    case “selectedOverSkin”:
    triangle.visible = false;
    triangle2.visible = true;
    break;
    case “selectedDownSkin”:
    triangle.visible = false;
    triangle2.visible = true;
    break;
    case “disabledSkin”:
    triangle.visible = false;
    triangle2.visible = true;
    break;

  8. Dave (Post author)

    How about percentWidth and percentHeight instead of unscaledWidth/height? I know it takes care of some issues with Modules. It might fix the issue you’re having with the triangles.

  9. Kareem

    Hi,

    Thanks for the example, I am trying to combine the Rounded button with the Gradient button examples and seem to be running into some issues with getting this to work.

  10. Dave (Post author)

    Hey Kareem,
    What kind of issues are you running into? Adobe posted some new stuff since this post http://livedocs.adobe.com/flex/3/html/help.html?content=skinning_6.html

    Take a look and see if that helps.

  11. Pingback: How to UnFlex Your Flex – Making Flex applications look and feel unique

  12. Joseph

    This is a great walkthrough. Thank you, it was very helpful. There are a couple of important things to note….

    a) if you read the bottom post at this link:

    http://stackoverflow.com/questions/1732060/flex-linkbar-how-to-set-a-selected-buttons-background-color

    you can see that it mentions:

    the source code of LinkBar:

    // Hilite the new selection.
    child = Button(getChildAt(selectedIndex));
    child.enabled = false;

    This means that technically disabledSkin would be used instead of downSkin I believe. So if you plan to use the gradient fill to make the buttons look pressed in when selected then you may want to modify david’s line:

    if(name != “downSkin”) {

    to:

    if(name != “downSkin” && name != “disabledSkin”) {

    b) the text color of the button that is selected can never be modified because the button that is selected is technically disabled, meaning the color of the disabled text will always be gray.

    If anyone has any further information on this please let us know. Thanks!

  13. Joseph

    solution for color of rollover text and selected text below….

    http://dezeloper.wordpress.com/2010/02/24/flex-css-linkbar-togglebuttonbar-selected-text-color/

  14. Pingback: flex | Pearltrees

Comments are closed.