With the new spark components in MXML 2009 you may have noticed there is not longer an "icon" style property you can set for a button. So what do you do if you want an icon in your button?
One solution is to put the icon in your button skin, but this would require you to build multiple skins for multiple icons.
Another solution is to use an Image component as a button. However, let's say we want to have different icons used for different rollover states. We could definitely use MouseEvent event listeners to switch the source property of the Image component. Unfortunately, transparent pixels in an image have been known to cause problems with MouseEvents firing properly. To avoid these type of pitfalls, we are going to create our own custom component by doing the following:
package com.tmg.components { import spark.components.Button; //icons [Style(name="iconUp",type="*")] [Style(name="iconOver",type="*")] [Style(name="iconDown",type="*")] [Style(name="iconDisabled",type="*")] //paddings [Style(name="paddingLeft",type="Number")] [Style(name="paddingRight",type="Number")] [Style(name="paddingTop",type="Number")] [Style(name="paddingBottom",type="Number")] public class IconButton extends Button { public function IconButton() { super(); } } }
In this class we define Style Metadata tags to let Flash Builder know what styles are available for this component in addition to the default style properties of Button. With this component, we want the developer to be able to specify images for different states of the button. We also want to give the developer the ability to specify the padding around the elements of our button.
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" currentStateChanging="onCurrentStateChanging(event)" > <fx:Metadata> [HostComponent("com.freckle.oceania.client.view.components.IconButton")] </fx:Metadata> <fx:Script> <![CDATA[ import mx.events.StateChangeEvent; private function onCurrentStateChanging(event:StateChangeEvent):void{ switch(event.newState){ case "up": setIcon("iconUp"); break; case "over": setIcon("iconOver"); break; case "down": setIcon("iconDown"); break; case "disabled": setIcon("iconDisabled"); break; } } private function setIcon(type:String):void{ if(hostComponent.getStyle(type) != null){ icon.source = hostComponent.getStyle(type); } } ]]> </fx:Script> <s:layout> <s:BasicLayout/> </s:layout> <s:states> <s:State name="up"/> <s:State name="over"/> <s:State name="down"/> <s:State name="disabled"/> </s:states> <s:Rect left="0" right="0" top="0" bottom="0" width="69" height="20" radiusX="2" radiusY="2"> <s:stroke> <s:SolidColorStroke id="outline" weight="1"/> </s:stroke> <s:fill> <mx:LinearGradient> <mx:GradientEntry color="#ffffff" ratio="0"/> <mx:GradientEntry color="#cccccc" ratio="1"/> </mx:LinearGradient> </s:fill> </s:Rect> <s:Group horizontalCenter="0" verticalCenter="0" > <s:layout> <s:HorizontalLayout paddingBottom="{ hostComponent.getStyle('paddingBottom')}" paddingTop="{ hostComponent.getStyle('paddingTop')}" paddingLeft="{ hostComponent.getStyle('paddingLeft')}" paddingRight="{ hostComponent.getStyle('paddingRight')}" /> </s:layout> <mx:Image id="icon" source="{hostComponent.getStyle('iconUp')}" verticalCenter="0" alpha="{(this.currentState == 'up')?.5:1}" /> <s:SimpleText text="{hostComponent.label}" verticalCenter="0" includeInLayout="{( hostComponent.label != '' )}" visible="{( hostComponent.label != '' )}"/> </s:Group> </s:SparkSkin>
One of the first things you may notice is the Metadata tag declaring the Hostcomponent. This is important because it lets this skin component know what kind of component will be using this skin. Note the SimpleText component's text property is set to the hostComponent's label property.
Another key element to this piece of code is the event handler for when the state changes. As you may have guessed from the contents of the states tag, the hostComponent changes the state of this skin for the different button mouse states. By listening to this event, we can change the current source of the icon image component.
This brings us to our last step...
<components:IconButton width="100%" iconUp="assets/images/icons/toolbar/arrow_out.png" iconDisabled="assets/images/icons/toolbar/arrow_out_disabled.png" fontFamily="Times" fontWeight="bold" skinClass="com.tmg.skins.IconButtonSkin" paddingLeft="5" paddingTop="5" paddingRight="5" paddingBottom="5" />
When implementing your own skins, be sure to set the skinClass property. In this implementation we just set the iconUp and iconDisabled properties. Because of how we wrote the set icon method in our skin, the skin will only use the icons that have been declared. As for the label, we have also coded the skin to not show the text component if the label text is empty, but in this case we do have a label.
So there you have it, a brand new component that uses the new MXML 2009 Spark architecture for skinning. With this new approach, we can break out our UI code into it's own file/component. A benefit of this new method is that we don't have much code in the actual component class. By using component skins, we can not only define the look via MXML but we can also add some logic/code. For example, it would be really easy to add a method that spins the icon on rollover or to change the text of label when disabled.
For more information about this, check out: https://xd.adobe.com/#/videos/video/165
Here’s a great example of how to incorporate transitions and more advanced effects into your skin:
http://www.hulstkamp.com/2009/06/18/spark-icon-button-with-gradient-effects-and-filter-animation-colorized-by-styles-flex-4-gumbo/403
[...] Icon Buttons have shown up in the past few days. Andy mcintosh did an example here, Rob another one here, I did one myself here. While all were using a slightly different approach the motivation behind [...]
[...] new TitleWindow, much like the lack of icon property in a Spark button I wrote about in a previous post. In this tutorial, I'm going to demonstrate how to create a resizable TitleWindow using the new [...]
shouldn’t:
alpha=”{(this.currentState == ‘up’)?.5:1}”
be written as:
alpha=”1″ alpha.up=”0.5″
Daniel,
You are totally right about using alpha.up instead. Thanks for the heads up.
Is there a way to set a default skin class for the IconButton, so I don’t have to write out longhand
skinClass=”com.tmg.skins.IconButtonSkin”
every time I want an icon? I tried doing it via CSS, but I’m not sure on the specific format for skinning custom components, all the examples only show for Halo and Spark components, I think I’ve got the namespace wrong for custom components somehow
Hi,
first of all thank you for sharing this wunderfull solution!
One question: As soon as I add some text to the InconButton it looks uggly, as the text appears at the top.
I tried to add a verticalAlign=”middle” in the <s:SimpleText of <s:SparkSkin but that does not help.
Does someone have a solution for that?
Thanks!
Martin Z
@Daniel, @Ben
the change of
alpha=”{(this.currentState == ‘up’)?.5:1}”
to
alpha=”1″ alpha.up=”0.5″
does not work with me? Do I have to add something?
Martin Z
it will be compleacted for new programers do go so long way for getting such a effect? What you think?