Setting a solid tab fill on a Spark TabBar control in Flex 4

The following example shows how you can create a solid fill on a Spark TabBar control in Flex 4 by creating a custom TabBarButton skin and setting the skinClass style.

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/05/04/setting-a-solid-tab-fill-on-a-spark-tabbar-control-in-flex-4/ -->
<s:Application name="Spark_TabBar_TabBarButton_skin_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">
 
    <s:TabBar id="tBar"
            skinClass="skins.CustomSparkTabBarSkin"
            horizontalCenter="0" verticalCenter="0">
        <s:dataProvider>
            <s:ArrayList source="[The quick brown fox,jumps over,the lazy dog]" />
        </s:dataProvider>
    </s:TabBar>
 
</s:Application>

The custom Spark TabBar skin, skins/CustomSparkTabBarSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/05/04/setting-a-solid-tab-fill-on-a-spark-tabbar-control-in-flex-4/ -->
<s:Skin name="CustomSparkTabBarSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
        alpha.disabled="0.5">
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
 
    <fx:Metadata>
        [HostComponent("spark.components.TabBar")]
    </fx:Metadata>
 
    <fx:Script  fb:purpose="styling">
        <![CDATA[
            import mx.core.UIComponent;
 
            override protected function updateDisplayList(unscaledWidth:Number, unscaleHeight:Number):void {
                const numElements:int = dataGroup.numElements;
                const cornerRadius:int = hostComponent.getStyle("cornerRadius");
                for (var i:int = 0; i < numElements; i++) {
                    var elt:UIComponent = dataGroup.getElementAt(i) as UIComponent;
                    if (elt) {
                        elt.setStyle("cornerRadius", cornerRadius);
                    }
                }
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
        ]]>
    </fx:Script>
 
    <s:DataGroup id="dataGroup" width="100%" height="100%">
        <s:layout>
            <s:ButtonBarHorizontalLayout gap="-1"/>
        </s:layout>
        <s:itemRenderer>
            <fx:Component>
                <s:ButtonBarButton skinClass="skins.CustomSparkTabBarButtonSkin" />
            </fx:Component>
        </s:itemRenderer>
    </s:DataGroup>
 
</s:Skin>

The custom Spark TabBar button skin, skins/CustomSparkTabBarButtonSkin.mxml, is as follows:

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2010/05/04/setting-a-solid-tab-fill-on-a-spark-tabbar-control-in-flex-4/ -->
<s:SparkSkin name="CustomSparkTabBarButtonSkin"
        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.disabledStates="0.5">
    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" stateGroups="overStates" />
        <s:State name="down" stateGroups="downStates" />
        <s:State name="disabled" stateGroups="disabledStates" />
        <s:State name="upAndSelected" stateGroups="selectedStates, selectedUpStates" />
        <s:State name="overAndSelected" stateGroups="overStates, selectedStates" />
        <s:State name="downAndSelected" stateGroups="downStates, selectedStates" />
        <s:State name="disabledAndSelected" stateGroups="selectedUpStates, disabledStates, selectedStates" />
    </s:states>
 
    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.ButtonBarButton")]
    </fx:Metadata>
 
    <fx:Script fb:purpose="styling">
        <![CDATA[
            import spark.components.TabBar;
 
            static private const exclusions:Array = ["labelDisplay"];
 
            override public function get colorizeExclusions():Array {
                return exclusions;
            }
 
            override protected function initializationComplete():void {
                useChromeColor = true;
                super.initializationComplete();
            }
 
            private var cornerRadius:Number = 4
 
            private function updateBorderTop(width:Number, height:Number):void {
                var path:String = createPathData(true);
                borderTop.data = path;
            }
 
            private function updateSelectedHighlight(width:Number, height:Number):void {
                if (!selectedHighlightV) {
                    return;
                }
 
                var left:Number = -0.5;  // assuming stroke weight is 1.0
                var right:Number = width - 0.5;
 
                var path:String = createPathData(false);
                selectedHighlightV.data = path;
 
                // Configure the left/right sides of the two horizontal lines, defined with
                // s:Rects, that appear at the top of the selected highlight.
 
                selectedHighlightH1.x = selectedHighlightH2.x = left + cornerRadius;
                selectedHighlightH1.width = selectedHighlightH2.width = (right - left) - (2 * cornerRadius);
            }
 
            private function createPathData(isBorder:Boolean):String {
                var left:Number = -0.5;  // assuming stroke weight is 1.0
                var right:Number = width - 0.5;
                var top:Number = 0.5;
                var bottom:Number = height;
 
                var a:Number = cornerRadius * 0.292893218813453;
                var s:Number = cornerRadius * 0.585786437626905;
 
                // If the path is for the highlight,
                // Draw the vertical part of the selected tab highlight that's rendered
                // with alpha=0.07.  The s:Path is configured to include only the left and
                // right edges of an s:Rect, along with the top left,right rounded corners.
                // Otherwise, we draw a full path.
                var path:String = "";
                path +=  "M " + left + " " + bottom;
                path += " L " + left + " " + (top + cornerRadius);
                path += " Q " + left + " " + (top + s) + " " + (left + a) + " " + (top + a);
                path += " Q " + (left + s) + " " + top + " " + (left + cornerRadius) + " " + top;
 
                if (isBorder)
                    path += " L " + (right - cornerRadius) + " " + top;
                else
                    path += " M " + (right - cornerRadius) + " " + top;
 
                path += " Q " + (right - s) + " " + top + " " + (right - a) + " " + (top + a);
                path += " Q " + right + " " + (top + s) + " " + right + " " + (top + cornerRadius);
                path += " L " + right + " " + bottom;
 
                return path;
            }
 
            private function updateCornerRadius():void {
                var cr:Number = getStyle("cornerRadius");
                if (cornerRadius != cr) {
                    cornerRadius = cr;
                    fill.topLeftRadiusX = cornerRadius;
                    fill.topRightRadiusX = cornerRadius;
                    highlightStroke.topLeftRadiusX = cornerRadius;
                    highlightStroke.topRightRadiusX = cornerRadius;
                }
            }
 
            override protected function updateDisplayList(unscaledWidth:Number, unscaleHeight:Number):void {
                updateCornerRadius();
                updateSelectedHighlight(unscaledWidth, unscaledHeight);
                updateBorderTop(unscaledWidth, unscaledHeight);
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
        ]]>
    </fx:Script>
 
    <!--- layer 2: fill -->
    <s:Rect id="fill"
            left="1" right="1" top="1" bottom="1"
            topLeftRadiusX="4" topRightRadiusX="4"
            width="69" height="21">
        <s:fill>
            <s:SolidColor color="0xFF0000"
                          color.selectedUpStates="0xBBBDBD"
                          color.overStates="0xBBBDBD"
                          color.downStates="0xAAAAAA"
                          alpha="0.85"
                          alpha.overAndSelected="1" />
        </s:fill>
    </s:Rect>
 
    <!--- layer 5: highlight stroke (all states except down) -->
    <s:Rect id="highlightStroke"
            left="1" right="1" top="1" bottom="1"
            topLeftRadiusX="4" topRightRadiusX="4"
            excludeFrom="downStates">
        <s:stroke>
            <s:SolidColorStroke color="0xD8D8D8"
                    alpha.overStates="0.22"
                    alpha.selectedUpStates="0.33"
                    weight="1" />
        </s:stroke>
    </s:Rect>
 
    <!--- layer 6: highlight stroke, selected tab, alpha=0.0 when not selected -->
    <s:Path id="selectedHighlightV"
            left="1" right="1" top="1" bottom="1"
            width="69" height="21">
        <s:stroke>
            <s:SolidColorStroke weight="1"
                    color="0x000000"
                    alpha="0.0"
                    alpha.downStates="0.15"
                    alpha.selectedUpStates="0.15"
                    alpha.overAndSelected="0.15" />
        </s:stroke>
    </s:Path>
 
    <s:Rect id="selectedHighlightH1"
            top="1" height="1">
        <s:fill>
            <s:SolidColor color="0x000000"
                    alpha="0.0"
                    alpha.downStates="0.25"
                    alpha.selectedUpStates="0.25"
                    alpha.overAndSelected="0.25" />
        </s:fill>
    </s:Rect>
 
    <s:Rect id="selectedHighlightH2"
            top="2" height="1">
        <s:fill>
            <s:SolidColor color="0x000000"
                    alpha="0.0"
                    alpha.downStates="0.15"
                    alpha.selectedUpStates="0.15"
                    alpha.overAndSelected="0.15" />
        </s:fill>
    </s:Rect>
 
    <!--- layer 7: border - put on top of the fill so it doesn't disappear when scale is less than 1 -->
    <s:Line id="borderBottom"
            left="0" right="0" bottom="0">
        <s:stroke>
            <s:SolidColorStroke weight="1"
                    color="0x000000"
                    color.selectedStates="0x434343"
                    alpha="0.75"
                    alpha.down="0.85"
                    alpha.selectedStates="0.5" />
        </s:stroke>
    </s:Line>
 
    <s:Path id="borderTop"
            left="0" right="0" top="0" bottom="0"
            width="69" height="21">
        <s:stroke>
            <s:LinearGradientStroke rotation="90" weight="1">
                <s:GradientEntry color="0x000000"
                        alpha="0.5625"
                        alpha.down="0.6375"
                        alpha.selectedStates="0.6375" />
                <s:GradientEntry color="0x000000"
                        alpha="0.75"
                        alpha.down="0.85"
                        alpha.selectedStates="0.85" />
            </s:LinearGradientStroke>
        </s:stroke>
    </s:Path>
 
    <!-- 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>

[GoogleAdsWide]

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.

3 thoughts on “Setting a solid tab fill on a Spark TabBar control in Flex 4

  1. This is nice… but …. how do you get a spark tabbar to set colors to “individual tabs” ?

    I remember you gave an example of how this can be done with the MX tabBar like this…

    //loop through tabs and set each one using getChildAt
    //to return a tab object

    tab = Tab(tabBar.getChildAt(idx) );
    tab.setStyle(“chromeColor”,thiscolor);
    tab.setStyle(“fontWeight”,”bold”);

    Does anybody know of anything similar for Spark TabBar ?

Comments are closed.