Loading Flex Skins at Runtime

,,

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:

package
{
  import flash.display.Sprite;

  public class Wrapper extends Sprite
  {
    [Embed(source="flex_skins.swf",symbol="RadioButton_upIcon")]
    public var rbUpSkin: Class;
    [Embed(source="flex_skins.swf",symbol="RadioButton_downIcon")]
    public var rbDownSkin: Class;
    [Embed(source="flex_skins.swf",symbol="RadioButton_disabledIcon")]
    public var rbDisabledSkin: Class;
    [Embed(source="flex_skins.swf",symbol="RadioButton_overIcon")]
    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();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
...

request = new URLRequest(swfLib);
var context:LoaderContext = new LoaderContext();
context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
loader.load(request, context);

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:

StyleManager.getStyleDeclaration("Button").setStyle("upSkin", wrapper.rbUpSkin);
StyleManager.getStyleDeclaration("Button").setStyle("downSkin", wrapper.rbDownSkin);
StyleManager.getStyleDeclaration("Button").setStyle("disabledSkin", wrapper.rbDisabledSkin);
StyleManager.getStyleDeclaration("Button").setStyle("overSkin", wrapper.rbOverSkin);

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).

 

14 Responses to Loading Flex Skins at Runtime

  1. Lance says:

    Wow! very nice! I tried this type of thing a couple weeks back and wan’t able to do it. The missing key for me was the bit with the application domain setting. Thanks for posting this example!

  2. Al says:

    Nice solution. However, I’d like to see it without the wrapper compiled in Flex. Is it possible to load an swf from Flash 9 Professional (in AS 3)?

  3. Brian Riggs says:

    Al, I’m not aware of a way to compile the wrapper without Flex (which doesn’t mean there isn’t a way ;-). But keep in mind that the Flex SDK is free, so you could use it to compile the wrapper SWF from the command line.

  4. Alaric says:

    Thanks for the reply. I realize the SDK is free, and that is definitely an option. However, I’d like to enable designers who know Flash and not Flex to create assets that I can use immediately. I’ve used Flash 9 to create an asset and export it as a class, but have had no luck loading it and using it at runtime in Flex…theoretically possible, but no concrete example yet.

  5. Andersson says:

    Can someone please post a working zip exampel ?:-)

  6. Brian Riggs says:

    Andersson, if you launch the demo app, right-click, and select “View Source”, you’ll see a link in the lower-left that allows you to download the source of the application.

  7. Sheri says:

    Excellent idea! I tried to compile your sample and it did work fine. But, when I created a wrapper using Flex 2 IDE, the class couldn’t be loaded. Is there a special setting to build the wrapper? I am really puzzled…

  8. Barrr says:

    Try to load example skin into this example app about 15-25 times or about 6 times if clicking not very fast and IE or other browse will crashed with error in FIDbg9.ocx file.I think its a bug into Loader.

  9. Austin says:

    As a note, you can take this same concept and apply it just using a stylesheet:Create an mxml file and then compile that as an SWF. Why do this?Because you don’t need to call the static StyleManager class to apply the skin at runtime. You literally load in the SWF and the skin gets applied to all of your widgets. This way you can keep ALL of the skinning in the CSS file and not have to apply individual Class definitions in an actionscript file.This way designers can modify the css stylesheet without having to create an actionscript code class and then you just rebuild the SWF using Ant. It keeps it a bit cleaner as I can see.

  10. Austin, your above post sounds very promising. Do you have a working sample?I tried to do what you described like so:The Flex compiler wouldn’t process any of my styling because I didn’t have component instances for all the different controls I was skinning.I don’t want to create an asset SWF that has every Flex component in existance instantiated to force the CSS styling to be processed.So, how do you do it?

  11. Austin says:

    Hi, I dont have a sample necessarily… but its really simple.1. Create the MXML file2. In styles.css:.MyButton{disabledSkin: Embed(source=”images/MyButton_disabledSkin.png”,scaleGridLeft=”32″, scaleGridTop=”4″, scaleGridRight=”60″, scaleGridBottom=”18″);downSkin: Embed(source=”images/MyButton_downSkin.png”,scaleGridLeft=”32″, scaleGridTop=”4″, scaleGridRight=”60″, scaleGridBottom=”18″);overSkin: Embed(source=”images/MyButton_overSkin.png”,scaleGridLeft=”32″, scaleGridTop=”4″, scaleGridRight=”60″, scaleGridBottom=”18″);upSkin: Embed(source=”images/MyButton_upSkin.png”,scaleGridLeft=”32″, scaleGridTop=”4″, scaleGridRight=”60″, scaleGridBottom=”18″);}then you have a skin loader, similar to:public static function loadSkin( aSkinPath:String ):void {if( ! loader ){loader = new Loader();loader.contentLoaderInfo.addEventListener(Event.INIT, onSkinLoaded);loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);}try {request = new URLRequest( aSkinPath );var context:LoaderContext = new LoaderContext();context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);loader.load( request, context );} catch ( e:Error ){}}This loads in the SWFThen, once the skin is loaded….public static function onSkinLoaded( event:Event ):void {broadcaster.notifyListeners( new SkinLoadedEvent( new Array( “” ), “”));}THEN you instantiate the visual objects where upon they will now use the styled CSS file.You can also specify the button to use the “MyStyle” definition in the css file and it will havethe new scale 9 skin that you loaded.Then, the only thing you need to do later is to redraw the visual objects if you change the themeso that flex can properly measure.

  12. Mickael Shahinian says:

    Dear friends,i am trying to use binding for upSkin property in button. Is that possible?say i have an xml file with node for all images and i want to bind that to upSkin for buttons created via Repeater tag.Thank you in advance.Mika

  13. Juan Sanchez says:

    This information was most helpful. Doesn’t seem as hard to switch skins now since Flex 2.0.1 is released.Also, just thought I’d share a website I created dedicated to Flex 2 themes, skins and styles. You can see it at http://www.scalenine.com.Thanks.Juan