There are lots of great resources out there on how to skin your Flex applications. Quick recap: Flex supports two approaches to skinning: graphical and programmatic. Graphical skinning involves creating graphical assets in Flash, Photoshop, Fireworks, etc. and embedding them in your Flex application. Programmatic skinning involves creating an ActionScript class which defines the skin for a control. As you might guess, graphical skinning is easier, programmatic skinning more powerful.
One drawback to both approaches is that the skinned resources (SWF/PNG/GIF/etc. for graphical skins, the AS class file for programmatic skins) must be available at compile-time for the skins to be applied. Or do they? In this post I’ll describe a neat trick for pulling in graphical skins at run-time (using a live demo with source code).
To make this example as simple as possible, I’m going to create a Flex app that allows the Button control to be dynamically skinned. This app will pull down a skinning SWF at runtime, load the skins, and apply them to the Button control. Again, to keep it simple I’m going to use the skin template file provided in NJ’s skinning article, and apply the RadioButton skins to the Button control. (A tip of the cap to Roger Gonzalez and NJ for basically coming up with this solution.)
Step #1: Create a wrapper SWF for the skin assets
The skin assets are in the aforementioned skin template file. I want to create a wrapper SWF that my Flex app can load at runtime and from which it can extract the appropriate assets, in this case the four symbols for the RadioButton control. Here’s the source for wrapper SWF:
public class Wrapper extends Sprite
public var rbUpSkin: Class;
public var rbDownSkin: Class;
public var rbDisabledSkin: Class;
public var rbOverSkin: Class;
Step #2: Put the wrapper SWF on your web server
The Flex app needs to load the wrapper SWF from somewhere!
Step #3: In your Flex app, use a Loader to load the wrapper SWF
I created a utility class called ClassLoader to wrap up functionality related to loading the SWF and extracting the class. Here are the key lines:
loader = new Loader();
request = new URLRequest(swfLib);
var context:LoaderContext = new LoaderContext();
context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
Notice that the Loader loads the SWF into an ApplicationDomain that has the current domain as its parent. This is the key to ensuring that the app can access the loaded class and its assets.
Step #4: Get the class from the loaded SWF and instantiate it
Here we load the Class using an agreed-upon className (in this case, "Wrapper"):
var wrapperClass:Class = loader.contentLoaderInfo.applicationDomain.getDefinition(className) as Class;
var wrapper:Object = new wrapperClass();
Step #5: Apply the skins using setStyle
You can apply the skins to a particular instance of Button or to all instances of Button. Here’s the latter approach:
Step #6: Run the app
My sample app (running live here, with "View Source" enabled) displays a text field where you can enter the URL of the wrapper SWF (also live here). Enter the wrapper SWF’s URL, click the "Apply" button, and you’ll see that the button now looks like an oversized radio button. As you mouse over or click on the button, you’ll notice that its appearance changes to display the appropriate skin. If you create your own wrapper SWF which exposes a different set of symbols with the same class names (rbUpSkin, etc.), you could point the sample app at it and have it load and display a different set of skins.
So why would you want to do this? Dynamic skinning gives you one incredibly powerful benefit: you can let your users put their skins on your app. Imagine an MP3 player like Winamp built in Flex. The developer doesn’t need to create a library of skins for the app, he/she can just expose a UI for setting the skin SWF (perhaps in the Preferences dialog) and let the user pick and choose from whatever skins the community comes up with. Equally important, the developer has fine-grained control over which controls can be skinned and which should retain their original skins (by only calling setStyle on the desired controls). And last but not least, keeping the skins outside of the application SWF will keep the application file size from exploding as the skins proliferate (with a minor but certainly-acceptable-to-the-user performance hit when the skins are loaded).