Creating an image only mobile button skins for Flex 4 (AIR Mobile)

One of the main differences when creating mobile Flex AIR apps from normal Flex Apps is that it’s better to use ActionScript only skins. With that in mind I’ve created a simple skin for image buttons.

Why this skin is better than using the default spark.skins.mobile.ButtonSkin? The answer is quite simple – performance. Since this is going to be an image only skin, we don’t need label element and all the other functionality the default mobile button skin has.

In order to use the class you just create a new skin extending the ImageButtonSkin. The properties you have to set inside tour constructor are upBorderSkin, downBorderSkin, measuredDefaultWidth and measuredDefaultHeight.

For the border skin properties you can use – images, SWF symbols, FGX files

Usage example:

public class ExampleButtonSkin extends ImageButtonSkin {

    [Embed(source="/assets/buttons/Example_up.png")]
    private var up:Class;

    [Embed(source="/assets/buttons/Example_down.png")]
    private var down:Class;
    
    public function ExampleButtonSkin ButtonSkin() 
    {
        super();
        upBorderSkin = up;
        downBorderSkin = down;
        measuredDefaultWidth = 230;
        measuredDefaultHeight = 80;
    }
}

If you want to use FXG border skins then you don’t need up and down variabled. You can set the FXG classes on the upBorderSkin and downBorderSkin directly.

ImageButtonSkin class:

package
{
    import mx.core.mx_internal;
    import flash.display.DisplayObject;
    import spark.components.supportClasses.ButtonBase;
    import spark.skins.mobile.supportClasses.MobileSkin;
    
    /**
     * Basic class for buttons skinning.
     * Set measuredDefaultWidth and measuredDefaultHeight in constructors.
     * @author Angel Vladov http://avladov.com
     */
    public class ImageButtonSkin extends MobileSkin
    {
        protected var upBorderSkin:Class = null;
         
        protected var downBorderSkin:Class = null;
        
        private var changeFXGSkin:Boolean = false;
        
        private var m_border:DisplayObject;
        private var borderClass:Class;
        
        private var enabledChanged:Boolean = false;
        private var m_hostComponent:ButtonBase;
        
        public function get hostComponent():ButtonBase {
            return m_hostComponent;
        }

        public function set hostComponent(value:ButtonBase):void {
            m_hostComponent = value;
        }
        
        override public function set currentState(value:String):void {
            var isDisabled:Boolean = currentState && currentState.indexOf("disabled") >= 0;
            
            super.currentState = value;
            
            if (isDisabled != currentState.indexOf("disabled") >= 0) {
                enabledChanged = true;
                invalidateProperties();
            }
        }
        
        override public function styleChanged(styleProp:String):void {
            if (!styleProp || styleProp == "styleName") {
                invalidateSize();
                invalidateDisplayList();
            }

            super.styleChanged(styleProp);
        }
        
        override protected function commitProperties():void {
            super.commitProperties();

            if (enabledChanged) {
                commitDisabled();
                enabledChanged = false;
            }
        }
        
        protected function commitDisabled():void {
            alpha = hostComponent.enabled ? 1 : 0.5;
        }
        
        override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void {
            // This should be empty. We don't want to have any background.
        }
        
        override protected function measure():void {
            measuredMinWidth = 0;
            measuredMinHeight = 0;
            measuredWidth = 0;
            measuredHeight = 0;
        }
        
        protected function get border():DisplayObject {
            return m_border;
        }
        
        override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void {
            super.layoutContents(unscaledWidth, unscaledHeight);
            
            // size the FXG background
            if (changeFXGSkin) {
                changeFXGSkin = false;
                
                if (m_border) {
                    removeChild(m_border);
                    m_border = null;
                }
                
                if (borderClass) {
                    m_border = new borderClass();
                    addChildAt(m_border, 0);
                }
            }
            
            layoutBorder(unscaledWidth, unscaledHeight);
        }
        
        private function layoutBorder(unscaledWidth:Number, unscaledHeight:Number):void {
            setElementSize(border, unscaledWidth, unscaledHeight);
            setElementPosition(border, 0, 0);
        }
        
        override protected function createChildren():void {
            // Empty body - we don't want to display label.
        }
        
        override protected function commitCurrentState():void {   
            super.commitCurrentState();
            
            borderClass = getBorderClassForCurrentState();
            
            if (!(m_border is borderClass)) {
                changeFXGSkin = true;
            }
            
            // update borderClass and background
            invalidateDisplayList();
        }
        
        protected function getBorderClassForCurrentState():Class {
            return currentState == "down" ? downBorderSkin : upBorderSkin;
        }
    }
}

This skin doesn’t take into account applicationDPI. You can add it the same way it’s used in spark mobile ButtonSkin.

Leave a Reply