Using a bitmap image skin in a Spark Button control in Flex 4

The following example shows how you can use a series state-aware of bitmap images in a Spark Button control in Flex 4 by creating a custom skin and setting the BitmapImage source property to different images in the various button states.

The following example(s) require Flash Player 10 and the Adobe Flex 4 SDK. To download the Adobe Flash Builder 4 trial, see www.adobe.com/products/flex/. To download the latest nightly build of the Flex 4 SDK, see opensource.adobe.com/wiki/display/flexsdk/Download+Flex+4.

For more information on getting started with Flex 4 and Flash Builder 4, see the official Adobe Flex Team blog.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/03/24/using-a-bitmap-image-skin-in-a-spark-button-control-in-flex-4/ -->
<s:Application name="Spark_Button_skinClass_BitmapImage_test"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
 
    <fx:Style>
        @namespace s "library://ns.adobe.com/flex/spark";
        @namespace mx "library://ns.adobe.com/flex/mx";
 
        s|Button#closeBtn {
            skinClass: ClassReference("skins.ImageButtonSkin");
        }
    </fx:Style>
 
    <s:Button id="closeBtn"
            horizontalCenter="0" verticalCenter="0" />
 
</s:Application>

And the custom Spark Button skin class, skins/ImageButtonSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/03/24/using-a-bitmap-image-skin-in-a-spark-button-control-in-flex-4/ -->
<s:SparkSkin name="ImageButtonSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
        minWidth="21" minHeight="21"
        alpha.disabled="0.5">
    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" />
    </s:states>
 
    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.Button")]
    </fx:Metadata>
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            /* Define the skin elements that should not be colorized.
            For button, the graphics are colorized but the label is not. */
            static private const exclusions:Array = ["labelDisplay"];
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            override protected function initializationComplete():void {
                useChromeColor = true;
                super.initializationComplete();
            }
        ]]>
    </fx:Script>
 
    <s:BitmapImage source="@Embed('assets/images/close_button_up.png')"
            source.over="@Embed('assets/images/close_button_over.png')"
            source.down="@Embed('assets/images/close_button_down.png')"
            left="0" right="0" top="0" bottom="0" />
 
    <!-- layer 8: text -->
    <s:Label id="labelDisplay"
            textAlign="center"
            verticalAlign="middle"
            maxDisplayedLines="1"
            horizontalCenter="0" verticalCenter="1"
            left="10" right="10" top="2" bottom="2" />
 
</s:SparkSkin>

[Famfamfam]

View source is enabled in the following example.

This entry is based on a beta version of the Flex 4 SDK and therefore is very likely to change as development of the Flex SDK continues. The API can (and will) change causing examples to possibly not compile in newer versions of the Flex 4 SDK.

16 thoughts on “Using a bitmap image skin in a Spark Button control in Flex 4

  1. Thanks!

    Note: The Spark BitmapImage component only supports embedded images in Flex 4.0.

    You could use mx:Image instead if you want to use dynamically loaded images, or you could check out Steven Shongrunden’s blog post, http://flexponential.com/2010/03/17/using-non-embedded-images-in-a-spark-bitmapimage/ and see if that helps.

    You could also point to the skinClass in the button attributes like so:

    This has been handy as I’ve been converting Flex 3 skins to Flex 4 button skin. In our case we have the original AI files that the graphics were created from. Using these you can export to FXG and lower the size of the swf.

  2. Dang it, my code was eaten. It would be nice if there was a note or something letting people know to convert their < and > characters before posting. :P

    <s:Button skinClass="CloseButtonSkin" width="13" height="13"
            left="12" top="11"
            buttonMode="true" useHandCursor="true" mouseChildren="false" />
  3. Hi Peter.

    Could you point out where can I find an explanation/purpose of the ‘fb:purpose’ attribute in the Script block?

    Thanks,
    Igor

    1. @Igor Borodin,

      I’m not sure where it is documented/blogged, but I can give you a rough idea of what I think it is doing (with the disclaimer that I could be quite wrong)…

      Consider the following snippet:

      <s:DropDownList id="ddl" prompt="Please select an item" cornerRadius="10">
          <s:dataProvider>
              <s:ArrayList source="[one,two,three,four,five,six,seven]" />
          </s:dataProvider>
      </s:DropDownList>

      Using Flash Builder 4, if you right click the s:DropDownList text in code view, and select “Open Skin Declaration” from the context menu. The
      DropDownListSkin.mxml file opens in a new tab, and it is about 163 lines of code (I’m using the release 4.0 SDK (build 14159)). You can see an <fx:Script fb:purpose="styling">...</fx:Script> block with about 52 lines of code which sets things like border visibility, viewport insets, drop shadows, corner radius, and border colors/alphas.
      Now, set the skinClass style on the DropDownList in MXML, as seen in the following example:

      <s:DropDownList id="ddl" prompt="Please select an item" skinClass="" cornerRadius="10">
          <s:dataProvider>
              <s:ArrayList source="[one,two,three,four,five,six,seven]" />
          </s:dataProvider>
      </s:DropDownList>

      When setting the skinClass style in MXML, Flash Builder 4 should hint “Create Skin…”. Select that option and you should see a New MXML Skin dialog. Give the custom MXML skin a new name, make sure the Host component is set to spark.components.DropDownList (and the skin is a copy of spark.skins.spark.DropDownListSkin), check the Remove ActionScript styling code check box, and click Finish to finish the wizard. Flash Builder 4 will create a new skin class with 115 lines of code (default skin class was 163 lines of code) and you can see that the <fx:Script fb:purpose="styling">...</fx:Script> block has been removed.

      So it looks like the fb:purpose attribute specifies which block(s) of code will get stripped from the default Spark skins when you select the “Remove ActionScript styling code” option from the New MXML Skin wizard. Removing the styling code [presumably] makes smaller/leaner skins, but also removes some skin functionality (in the DropDownList example above, you can see that setting the cornerRadius style no longer works after removing the <fx:Style> block.

      Peter

      1. Thank you very much, Peter.

        So, if I’ve understood you correctly, the actionscript block in the skin file enables/disables style attributes in the component MXML. Referring to your example, I still can set the ‘cornerRadius’ in the skin, but it will be hard coded.
        So, unless I want controlling style within the component’s MXML, I can safely check the ‘Remove ActionScript’ option in the New Skin dialogue.

        If that’s right, perhaps you could let people know through your blog about the purpose of ‘fb:purpose’.
        I didn’t mean bon mot.

        Thank you for your relentless work.
        Igor Borodin

  4. Hi Peter!

    What would be the best way to go for a series of buttons with the same structure (icon on the left, label on the right) but different icons for each? For instance, let’s say we have this Close button, but also a Help button, and a Back button. I could create a base Spark Button skin class and extend it for each case (icon), but… would there be any way to use a single class, and customize each button in the main class?

    Thanks! (as always!!)

    1. @Alexandre Madurell,

      The best way would probably be to extend the s:Button class and add whatever properties/styles you need and then create a custom skin. I don’t have time at the moment to give a full example, but here is a quick-n-dirty example which uses uses a custom Spark Button skin which adds a s:BitmapImage control to display an embedded icon. Then you can just use the styleName property and an icon style in a <Style> block:

      <?xml version="1.0" encoding="utf-8"?>
      <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
              xmlns:s="library://ns.adobe.com/flex/spark"
              xmlns:mx="library://ns.adobe.com/flex/mx">
       
          <fx:Style>
              .button1 {
                  icon: Embed("assets/barney.jpg");
                  skinClass: ClassReference("skins.CustomIconButtonSkin");
              }
              .button2 {
                  icon: Embed('assets/ingbear.jpg');
                  skinClass: ClassReference("skins.CustomIconButtonSkin");
              }
              .button3 {
                  skinClass: ClassReference("skins.CustomIconButtonSkin");
              }
              .button4 {
                  icon: Embed('assets/T-1132.jpg');
                  skinClass: ClassReference("skins.CustomIconButtonSkin");
              }
          </fx:Style>
       
          <s:HGroup horizontalCenter="0" verticalCenter="0">
              <s:Button label="Button 1 (barney)" styleName="button1" />
              <s:Button label="Button 2 (ingbear)" styleName="button2" />
              <s:Button label="Button 3 (no icon)" styleName="button3" />
              <s:Button label="Button 4 (T-1132)" styleName="button4" />
              <s:Button label="Button 5 (default skin)" />
          </s:HGroup>
       
      </s:Application>

      And the custom Spark Button skin, skins/CustomIconButtonSkin.mxml, is as follows:

      <?xml version="1.0" encoding="utf-8"?>
      <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009"
                   xmlns:s="library://ns.adobe.com/flex/spark" 
                   xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
                   minWidth="21" minHeight="21"
                   alpha.disabled="0.5">
          <!-- states -->
          <s:states>
              <s:State name="up" />
              <s:State name="over" />
              <s:State name="down" />
              <s:State name="disabled" />
          </s:states>
       
          <!-- host component -->
          <fx:Metadata>
              <![CDATA[
                  [HostComponent("spark.components.Button")]
              ]]>
          </fx:Metadata>
       
          <fx:Script fb:purpose="styling">
              <![CDATA[
                  /* Define the skin elements that should not be colorized. 
                  For button, the graphics are colorized but the label is not. */
                  static private const exclusions:Array = ["labelDisplay"];
       
                  override public function get colorizeExclusions():Array {
                      return exclusions;
                  }
       
                  override protected function initializationComplete():void {
                      useChromeColor = true;
                      super.initializationComplete();
                  }  
       
                  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void {
                      var cr:Number = getStyle("cornerRadius");
       
                      if (cornerRadius != cr) {
                          cornerRadius = cr;
                          shadow.radiusX = cornerRadius;
                          fill.radiusX = cornerRadius;
                          lowlight.radiusX = cornerRadius;
                          highlight.radiusX = cornerRadius;
                          border.radiusX = cornerRadius;
                      }
       
                      if (highlightStroke) {
                          highlightStroke.radiusX = cornerRadius;
                      }
                      if (hldownstroke1) {
                          hldownstroke1.radiusX = cornerRadius;
                      }
                      if (hldownstroke2) {
                          hldownstroke2.radiusX = cornerRadius;
                      }
       
                      super.updateDisplayList(unscaledWidth, unscaledHeight);
                  }
       
                  private var cornerRadius:Number = 2;
              ]]>
          </fx:Script>
       
          <!-- layer 1: shadow -->
          <s:Rect id="shadow"
                  left="-1" right="-1" top="-1" bottom="-1" radiusX="2">
              <s:fill>
                  <s:LinearGradient rotation="90">
                      <s:GradientEntry color="0x000000" 
                                       color.down="0xFFFFFF"
                                       alpha="0.01"
                                       alpha.down="0" />
                      <s:GradientEntry color="0x000000" 
                                       color.down="0xFFFFFF" 
                                       alpha="0.07"
                                       alpha.down="0.5" />
                  </s:LinearGradient>
              </s:fill>
          </s:Rect>
       
          <!-- layer 2: fill -->
          <s:Rect id="fill"
                  left="1" right="1" top="1" bottom="1" radiusX="2">
              <s:fill>
                  <s:LinearGradient rotation="90">
                      <s:GradientEntry color="0xFFFFFF" 
                                       color.over="0xBBBDBD" 
                                       color.down="0xAAAAAA" 
                                       alpha="0.85" />
                      <s:GradientEntry color="0xD8D8D8" 
                                       color.over="0x9FA0A1" 
                                       color.down="0x929496" 
                                       alpha="0.85" />
                  </s:LinearGradient>
              </s:fill>
          </s:Rect>
       
          <!-- layer 3: fill lowlight -->
          <s:Rect id="lowlight"
                  left="1" right="1" top="1" bottom="1" radiusX="2">
              <s:fill>
                  <s:LinearGradient rotation="270">
                      <s:GradientEntry color="0x000000" ratio="0.0" alpha="0.0627" />
                      <s:GradientEntry color="0x000000" ratio="0.48" alpha="0.0099" />
                      <s:GradientEntry color="0x000000" ratio="0.48001" alpha="0" />
                  </s:LinearGradient>
              </s:fill>
          </s:Rect>
       
          <!-- layer 4: fill highlight -->
          <s:Rect id="highlight"
                  left="1" right="1" top="1" bottom="1" radiusX="2">
              <s:fill>
                  <s:LinearGradient rotation="90">
                      <s:GradientEntry color="0xFFFFFF"
                                       ratio="0.0"
                                       alpha="0.33" 
                                       alpha.over="0.22" 
                                       alpha.down="0.12"/>
                      <s:GradientEntry color="0xFFFFFF"
                                       ratio="0.48"
                                       alpha="0.33"
                                       alpha.over="0.22"
                                       alpha.down="0.12" />
                      <s:GradientEntry color="0xFFFFFF"
                                       ratio="0.48001"
                                       alpha="0" />
                  </s:LinearGradient>
              </s:fill>
          </s:Rect>
       
          <!-- layer 5: highlight stroke (all states except down) -->
          <s:Rect id="highlightStroke"
                  left="1" right="1" top="1" bottom="1" radiusX="2" excludeFrom="down">
              <s:stroke>
                  <s:LinearGradientStroke rotation="90" weight="1">
                      <s:GradientEntry color="0xFFFFFF" alpha.over="0.22" />
                      <s:GradientEntry color="0xD8D8D8" alpha.over="0.22" />
                  </s:LinearGradientStroke>
              </s:stroke>
          </s:Rect>
       
          <!-- layer 6: highlight stroke (down state only) -->
          <s:Rect id="hldownstroke1"
                  left="1" right="1" top="1" bottom="1" radiusX="2" includeIn="down">
              <s:stroke>
                  <s:LinearGradientStroke rotation="90" weight="1">
                      <s:GradientEntry color="0x000000" alpha="0.25" ratio="0.0" />
                      <s:GradientEntry color="0x000000" alpha="0.25" ratio="0.001" />
                      <s:GradientEntry color="0x000000" alpha="0.07" ratio="0.0011" />
                      <s:GradientEntry color="0x000000" alpha="0.07" ratio="0.965" />
                      <s:GradientEntry color="0x000000" alpha="0.00" ratio="0.9651" />
                  </s:LinearGradientStroke>
              </s:stroke>
          </s:Rect>
       
          <s:Rect id="hldownstroke2"
                  left="2" right="2" top="2" bottom="2" radiusX="2" includeIn="down">
              <s:stroke>
                  <s:LinearGradientStroke rotation="90" weight="1">
                      <s:GradientEntry color="0x000000" alpha="0.09" ratio="0.0" />
                      <s:GradientEntry color="0x000000" alpha="0.00" ratio="0.0001" />
                  </s:LinearGradientStroke>
              </s:stroke>
          </s:Rect>
       
          <!-- layer 7: border - put on top of the fill so it doesn't disappear when scale is less than 1 -->
          <s:Rect id="border"
                  left="0" right="0" top="0" bottom="0" width="69" height="20" radiusX="2">
              <s:stroke>
                  <s:LinearGradientStroke rotation="90" weight="1">
                      <s:GradientEntry color="0x000000" 
                                       alpha="0.5625"
                                       alpha.down="0.6375" />
                      <s:GradientEntry color="0x000000" 
                                       alpha="0.75" 
                                       alpha.down="0.85" />
                  </s:LinearGradientStroke>
              </s:stroke>
          </s:Rect>
       
          <s:HGroup horizontalCenter="0" verticalCenter="1" left="10" right="10" top="2" bottom="2">
              <s:BitmapImage source="{hostComponent.getStyle('icon')}" verticalCenter="1" />
       
              <!-- layer 8: text -->
              <s:Label id="labelDisplay"
                       textAlign="center"
                       verticalAlign="middle"
                       maxDisplayedLines="1"
                       width="100%" height="100%" />
          </s:HGroup>
       
      </s:SparkSkin>

      Peter

      1. But yeah, extending s:Button and adding a style would definitely be cleaner since you could do something like <local:IconButton label="Button w/ icon" icon="@Embed('assets/icon.png')" /> instead of having to use a bit awkward style names.

        Peter

      2. Awesome!!! Thanks a bunch for such prompt answer!!!
        I think the example you provided will do greatly in our project (although I’ll meditate on your <local:IconButton… solution too) ;)

  5. Hi,

    I found this post indredibley useful but became stuck when trying to dynamically change the icon when the buton was clicked.

    To save others who are new to this having to go through the same itterations .. here are the additions I made ..

    A) Included a set of class declarations in my application for each icon .. they look like:

    [Embed(source=’images/pause.jpg’)]
    public static var pauseStart:Class;
    [Embed(source=’images/play.jpg’)]
    public static var playStart:Class;

    B) Then in the click handler have something like :

    if(e.target.label==”Pause”){
    e.target.label=”Play”;
    e.target.setStyle(“icon”, playStart);
    }else{
    e.target.label=”Pause”;
    e.target.setStyle(“icon”, pauseStart);
    }

    Simple when you know how, but not when you don’t realise that the icon has to reference a class

    Jim

  6. Hi,

    Nice entry. Do you have any suggestion on how to define thethe hitarea area of a button via a transparent PNG? I have use the InteractivePNG Class (from mosesSupposes) for this before but am unsure how one would g about this in an mxml context?

  7. Hi Peter
    Thanks for the sample. You sample works with Emebeded image only, so we use instead of . Everything seems to work fine in Flex 4.

    Now we have Flex4.5. We tried to use instead of I got a interesting run time error saying the control “cannot be found”. Any idea what cause that?

    Thanks for any Info.

  8. Hi Peter
    Thanks for the sample. You sample works with emebeded image only, so we use mx:image instead of s:BitmapImage. Everything seems to work fine in Flex 4.

    Now we have Flex4.5. We tried to use s:image instead of s:BitmapImage. We got a interesting run time error saying the control “cannot be found”. Any idea what cause that?

    Thanks for any Info.

Comments are closed.