Skinning a Flex Mobile Button with Bitmaps

Flex Mobile Button Skin

Flex Mobile Button Skin

I’m working on a mobile app that requires a lot of skinning and since Flex 4 we’ve gotten a MUCH improved skinning model for Flex. Unfortunately, with Flex mobile, for performance reasons it’s a good idea to use ActionScript-only skins which means going back to the world of Flex 3 and diving into the component lifecycle a bit.

My colleague Terry Ryan has a good blog post that shows what he used to do skinning and I started off with that approach. But something didn’t seem quite right about skinning a button with a function associated with borders, so I tracked down the skinning master on the Flex team, Jason San Jose, and asked him the best way to do it. It turns out it’s really pretty simple to skin a button using Bitmap assets. If you look at the source code for ButtonSkin you’ll see that in the constructor, they associate FXG files (as classes) with specific button states.

upBorderSkin = spark.skins.mobile320.assets.Button_up;
downBorderSkin = spark.skins.mobile320.assets.Button_down;

In that constructor they also check for the DPI of the device and then load in the correct skin class according to that DPI. In general it’s going to be a good idea to check for different screen sizes and load in different assets accordingly, but for this example, I’ve just got one size that stretches reasonably well. The first step is to embed the assets and give them a variable name.

[Bindable]
[Embed(source="/assets/combobox_button_up.png")]
private var up:Class;
 
[Bindable]
[Embed(source="/assets/combobox_button_down.png")]
private var down:Class;

Then, in the constructor, simply set the upBorderSkin and downBorderSkin to those classes:

public function ComboBoxButtonSkin()
{
     super();
 
     upBorderSkin = up;
     downBorderSkin = down;
}

In this example I also wanted to create a graphic that would be overlaid on top of my skin. It’s a triangle graphic that will change depending on whether the app is on iOS or Android because of the different ways that those two platforms do ComboBoxes. On iOS you often have the app slide over to reveal the list (so the arrow would be pointing to the right) and on Android you often get a pop-up window with a list of choices (so the arrow would point down).

Adding Objects To Your Button Skin

To add something to your skin you need to go back and think about how Flex components work. You first want to make sure it’s added using the createChildren method. In this example I have already created the _triangle variable and instantiated it in the constructor.

override protected function createChildren():void
{
     super.createChildren();
     addChild(_triangle);
 
}

Then, once it’s added, you can start to modify it. In this example I wanted to make sure I could dynamically place the arrow depending on the height and width of my button. Luckily, the drawBackground method gives you an unscaledHeight and unscaledWidth, which you can use to calculate where the object should be placed. So all I had to do was tie into the drawing API, use those numbers, and voila, my skin now has the triangle.

override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
     var halfUnscaledHeight:int = Math.round(unscaledHeight/2);
 
     _triangle.graphics.beginFill(0xcee410);
     _triangle.graphics.moveTo(unscaledWidth-20,halfUnscaledHeight-5);
     _triangle.graphics.lineTo(unscaledWidth-10,halfUnscaledHeight);
     _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight+5);
     _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight-5);
     _triangle.graphics.endFill();
}

One of the benefits of this approach as opposed to just including the triangle in the skin is that the triangle kind of looked odd when it was stretched and morphed to different screen sizes. This way it’s independent of the bitmap skin and I can change the size according to the size of the underlying button.

Here’s what the finished button looks like and here’s the full code for you to grab.

Mobile Button Up State

Mobile Button Up State

Mobile Button Down Skin

Mobile Button Down Skin

    package skins
    {
    import flash.display.Sprite;
 
    import spark.skins.mobile.ButtonSkin;
 
    public class ComboBoxButtonSkin extends ButtonSkin
    {
    [Bindable]
    [Embed(source="/assets/combobox_button_up.png")]
    private var up:Class;
 
    [Bindable]
    [Embed(source="/assets/combobox_button_down.png")]
    private var down:Class;
 
    protected var _triangle:Sprite;
 
    public function ComboBoxButtonSkin()
    {
    super();
 
    upBorderSkin = up;
    downBorderSkin = down;
 
    _triangle = new Sprite();
    }
 
    override protected function createChildren():void
    {
    super.createChildren();
    addChild(_triangle);
 
    }
 
    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
    {
    var halfUnscaledHeight:int = Math.round(unscaledHeight/2);
 
    _triangle.graphics.beginFill(0xcee410);
    _triangle.graphics.moveTo(unscaledWidth-20,halfUnscaledHeight-5);
    _triangle.graphics.lineTo(unscaledWidth-10,halfUnscaledHeight);
    _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight+5);
    _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight-5);
    _triangle.graphics.endFill();
    }
 
    }
    }

Enabling iOS/BlackBerry/Android After Creating a Flex Mobile Project

I just noticed this today and figured it might be helpful. If you create a Flex Mobile project with Flash Builder 4.5.1 and choose to only enable one or two of the platforms, you can always go back and change your mind later on using the Properties Panel under Flex Build Packaging. Just select the platform you want and check the “Enable this target platform” box.