Thursday, April 17, 2008

Flex Skinning: Programmatic Skins (Part 2)

This is the second part of a multi-part series on Flex skinning. The first part can be found here. Today, I'll review Button skinning and then launch into a discussion about how to skin using code rather than graphics. When last I left off, I'd given a general introduction to Flex skinning by defining some terminology and then showing you how to use CSS styling and asset-based skinning to turn a button into anything we wanted. While there is a ton of possibility just between these two techniques, there is a third which allows you to accomplish everything else under the sun: programmatic skinning.

What is Programmatic Skinning?

Programmatic skinning is a simple concept: instead of supplying art for a component's skin, you can provide code to draw it instead. The advantage you get from doing this in a programmatic way is that you can customize the drawing based on any number of factors: the component's state, style attributes, and even some randomization. So how do we do it? First, some review.

The Skinner's Mindset

Let's start back at the beginning when you wanted to skin a Button. The first thing you need to do is determine what is available to be skinned. Your best weapon is the Adobe Flex API Documentation. Looking up the Button class, you can see that there are eight skins:
  • upSkin
  • downSkin
  • overSkin
  • disabledSkin
  • selectedUpSkin
  • selectedDownSkin
  • selectedOverSkin
  • selectedDisabledSkin
And before you ask, yes, the documentation doesn't do the best job of dividing the skins out. They are clustered in with the CSS styling attributes. It just takes some experience to pick em out of the lineup. Generally, skin attributes will be suffixed with -Skin and be of type Class. OK, so once we have the skins identified, we have to ask ourselves: what do I want to skin? As I said last time, the selected- skins aren't necessary unless your button is a toggle. You can also skip disabledSkin if your app is simple and will never need to disable the button. However, I do recommend supplying a skin anyway, unless you are only using CSS styling, in which case it's automatically handled by Flex by dimming your Button for you. For this example, we'll just do the first four skins. Let's write a programmatic skin!

Writing a Programmatic Skin

When working with a designer, you will often times get the skin assets handed to you as PNG's or SWF's or some other file. You only need to write a programmatic skin for the following scenarios:
  • If you're really concerned about file size. (Assets can bulk up your deliverable.)
  • If the skin is dependent on dynamic data.
  • If your skins are all very similar and code can account for the differences.
It's based on this last point that many programmatic skins are made. Our example will show that. First, our application:
<mx:application mx="http://www.adobe.com/2006/mxml" layout="absolute">
  <mx:Style source="styling.css" />
  <mx:button horizontalcenter="0" verticalcenter="0" label="Dynamic!" />
</mx:application>
Nothing is different. Again, the CSS file will be changed. Here's what it will look like:
Button {
 color: white;
 text-roll-over-color: white;
 text-selected-color: white;
 up-skin: ClassReference('skins.WackyButtonSkin');
 down-skin: ClassReference('skins.WackyButtonSkin');
 over-skin: ClassReference('skins.WackyButtonSkin');
 disabled-skin: ClassReference('skins.WackyButtonSkin');
}
If you're a beginning skinner, I'm guessing you have exactly 4 questions. Let's see if I'm right: If you're not doing skins for the selected- versions, why are you styling text-selected-color? This is because Flash is using text-selected-color when you're clicking down on a Button. This is the equivalent of a mouse down. In essence, Flex could have skipped downSkin and just reused selectedDownSkin. So it's a little different with text color and, well, we just have to deal. You talk about attributes like upSkin, but in the CSS you use the syntax up-skin. What gives? Good question. In order to be as similar to CSS as possible, the Flex guys decided to make a naming conversion between their attributes. So, you can say upSkin or up-skin, for example, and the same attribute will be set. I'm one of those developers that prefers using the CSS style, but everyone's different and it's just a matter of taste. What is ClassReference? Actually, I'm betting you've figured this out, but I'll answer you anyway. Just like we used Embed() for assets, we use ClassReference() for code. Plain and simple. ClassReference takes a string which is the fully qualified name of the class that you want to handle the skin. Why is skins.WackyButtonSkin being specified for every skin type? Won't they all look the same?! You'll understand it when you write it! :-) Create a file named WackyButtonSkin.as and place it in a skins/ folder in your own source folder. Here it is:
package skins {
 import mx.core.EdgeMetrics;
 import mx.skins.Border;
 
 public class WackyButtonSkin extends ProgrammaticSkin {
  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
   graphics.clear();
   graphics.lineStyle(2, 0x0000ff);
   
   switch (name) {
    case "upSkin":
     graphics.beginFill(0xaa0000, 0.5);
     break;
     
    case "overSkin":
     graphics.beginFill(0xff0000, 0.5);
     break;
     
    case "downSkin":
     graphics.beginFill(0x440000, 0.5);
     break;
     
    case "disabledSkin":
     graphics.beginFill(0x444444, 0.5);
     break;
   }
   
   graphics.drawRoundRect(0, 0, unscaledWidth, unscaledHeight, 10, 10);
   graphics.endFill();
  }
 }
}
If you read the code carefully, you should be able to figure out exactly what's going on. Let's cover some key points:
  • Our skin extends the ProgrammaticSkin base class. More on this later.
  • We override updateDisplayList very similarly to when you extend other Flex components directly. (Ever extend Canvas in AS3?)
  • The name property tells us what skin this code is being used for. This is extremely useful!
That last bit can't be emphasized enough, and hopefully now you understand why we can create one programmatic skin for all the states of the Button. Since the Button is going to look similar in each state, we can differentiate the skin color based on either of the four states. If we were to skin this Button with assets, we'd have four separate graphics to maintain. Once change in any of them would require re-doing the other three. Here, we can make changes to the skin and have it reflect everywhere automatically! Here's the skin in action:

Now, let's say that we were making this skin for a corporate Web site, and it was decided that we should theme the buttons to go along with their logo. So, let's say they want the buttons to contain racing stripes. Now, if all four skins were specified with assets, our designer would have to change each one of them. However, we did it programmatically, so we can make the code change in one place as so:

package skins {
 import mx.skins.Border;
 
 public class WackyButtonSkin extends ProgrammaticSkin {
  private static const RACING_STRIPES_MARGIN:int = 2;
  
  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
   graphics.clear();
   
   switch (name) {
    case "upSkin":
     graphics.beginFill(0xaa0000, 0.5);
     break;
     
    case "overSkin":
     graphics.beginFill(0xff0000, 0.5);
     break;
     
    case "downSkin":
     graphics.beginFill(0x440000, 0.5);
     break;
     
    case "disabledSkin":
     graphics.beginFill(0x444444, 0.5);
     break;
   }
   
   graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
   graphics.endFill();
   
   // draw design lines
   graphics.lineStyle(1, 0xffffff, 0.15);

   graphics.moveTo(0, RACING_STRIPES_MARGIN);
   graphics.lineTo(unscaledWidth, RACING_STRIPES_MARGIN);
   
   graphics.moveTo(0, unscaledHeight - RACING_STRIPES_MARGIN - 1);
   graphics.lineTo(unscaledWidth, unscaledHeight - RACING_STRIPES_MARGIN - 1);
  }
 }
}
Which results in:

And there we go! The example is simple, but the result should be clear as day. Now, all the skins have the racing stripes thanks to a change in ONE place. Better yet, we can adjust the stripes over the lifetime of the skin and only need to do it in one place! That's re-usability, even at a graphical level.

Let's Review

Let's take a breather real quick and review what's going on. In order to skin our Button, we created a skin class that extended ProgrammaticSkin. Do we always have to? The answer is no. You have a few choices. First, we could have used UIComponent. This is the top-level class for all UI components in Flex. It's not used as often, though, because it has more classes on the inheritance chain than the other options. Most notably, it's a Container, and most skins don't need that extra weight. (The Drawing API does not require containment.) Second, we could use ProgrammaticSkin, which we did use. This is the most common option. Lastly, we could have used Border or RectangularBorder. These classes extend ProgrammaticSkin and add some functionality that allows you to expand the size of the components via EdgeMetrics. For the most part, even if you were skinning a border such as we are doing with Button, you likely won't need this class. RectangularBorder also has the added ability to skin the background with a graphic. It should be said that all of our options are so similar that we could have substituted ProgrammaticSkin with any of the other options and we'd have been totally fine -- the results would have been the same. New skinners often trip up on this point as they can be daunted by which base class to pick. In most scenarios, it isn't going to matter. Given that, I recommend ProgrammaticSkin as your first choice by default, unless you need some other specific functionality. It's the most compact solution and will work for 95% of your needs. So, when would you choose one of the others? You'd likely choose UIComponent if you wanted a "stateful" skin, which is a skin that can do transitions between states. You'd choose Border or RectangularBorder if you needed to be more specific on the size of the component.

This multi-part series on Flex skinning will continue. Over the next few weeks, I'll provide more compact examples of skinning that will include not just Buttons, but other components, too. I'll also cover "stateful" skins as well as putting the Border and RectangularBorder base classes to good use.

9 comments:

Dave said...

Thanks for this bro, you've just saved me hours of reading boring documentation!

Jake Lyman said...

Thanks, man, for posting. Great stuff! Do you know if there is a way to draw a shape on top of the button? I'd like to add a triangle to a programmatic toggle button so that it looks like an accordion button.

Is something like that possible with this approach or do I need to stick with a .png or .swc skinning approach?

Thanks again!

remz said...

Thanks man, this was really helpful, i just have one question.

What if i wanted to create a programmatic skin with graphical assets (e.g. a movieclip), is it possible to do this?

thank you very much

xw said...

Lops are one of the top damage classes of dofus kamas, without need of huge buffing. I know vitality is very tempting in kamas, you get after level 100 every time you level. You may be feeling intelligence in cheap kamas. You are going to be getting crab pincers which like dofus gold, depending on which server you are on. buy dofus kamas can help you get a high level in short time.

huyuni said...

nike shoes & Puma Shoes Online- tn nike,puma shoes,puma cat, baskets cheap nike shox, air max.cheap nike shox r4 torch, cheap nike air, nike running shoes air max, puma speed and more. Paypal payment.nike running shoes Enjoy your shopping experience on Nike & Puma Shoes Online Store.

huyuni said...

if you or your friend want to China Wholesaleplease cheack you can buy buy products wholesale here from wholesale from china ,the China Wholesalers on China Wholesale wholesale from china buy products wholesale China Wholesalers

huyuni said...

Cheap Brand Jeans ShopMen Jeans - True Religion Jeans, burberry polo shirtsGUCCI Jeans, Levi's Jeans, D&G Jeans, RED MONKEY Jeans, Cheap JeansArmani Jeans, Diesel Jeans, Ed hardy Jeans, Evisu Jeans, Women JeansJack&Jones Jeans...Lacoste Polo Shirts, , Burberry Polo Shirts.wholesale Lacoste polo shirts and cheap polo shirtswith great price. clothingol.com offers lot of 10 lacoste polo shirts and lot of 20 cheap polo shirts. clothingol.com offers classic fit polo shirts. polo clothing

huyuni said...

Charlestoncheap columbia jackets. turned a pair of double plays to do the trick. spyder jacketsThe had at least one runner on in every inning but the first and outhit the RiverDogs by a 12-6 margin Lawal should be a focal point of the Yellow cheap polo shirts along with highly touted newcomer, 6-9 Derrick Favors, rated as the No. 1 power forward on the ESPNU 100. The Yellow Jackets

huyuni said...

quelle chaussures pumapaire de chaussures pour hommes choisir?!? La réponse est toute simple:chaussures nike une paire de baskets en partie vernies. du 17ème au 5ème rang, qui lui offre une place dans un top 5 largement tn requindominé par les sites de vente de produits high-tech. Le site de réservation d'hôtels Bookings.com gagne pour sa part 47 places et atteint le 8ème rang.