如何设置 TabNavigator 按钮的样式或外观?

How do I style or skin the TabNavigator buttons?

我想在 Flex 4 中为 TabNavigator 添加一个图标和一个关闭按钮,但无法在线找到任何示例(所有示例均适用于 Flex 3)。

<mx:TabNavigator id="firstViewStack" 
                 width="100%"
                 height="100%"
                 clipContent="true">

    <s:NavigatorContent label="FIRST TAB"  >
        <s:Group width="100%" />
    </s:NavigatorContent>

</mx:TabNavigator>

ZOMGWTH?!?!?!这花了太长时间才弄清楚。这是设置默认选项卡皮肤的方式:

mx|Tab {
    skin:ClassReference("mx.skins.spark.TabSkin");
}

您可以将该皮肤复制到您自己的项目中并根据需要进行修改:

这里有一些例子。这会修改所有 TabNavigators 中的所有选项卡:

mx|Tab {
    skin:ClassReference("mySkins.MyTabSkin");
}

只设置一个 TabNavigator 的皮肤:

<mx:TabNavigator id="tabBarNavigator" styleName="myTabNavigator">

    <s:NavigatorContent label="First Tab"  >
            <s:Group width="100%" />
    </s:NavigatorContent>

    <s:NavigatorContent label="Second Tab">
        <s:Group height="100%" width="100%" />
    </s:NavigatorContent>
</mx:TabNavigator>


<fx:Style>
    @namespace s "library://ns.adobe.com/flex/spark";
    @namespace utils "com.flexcapacitor.utils.*";
    @namespace mx "library://ns.adobe.com/flex/mx";

    .myTabNavigator {
        tabStyleName: "myTabs";
    }

    .myTabs {
        skin:ClassReference("mySkins.MyTabSkin");
    }
</fx:Style>

这适用于 Flex 4.6 和 Spark 主题。

更新二:
这里有一些更值得注意的样式:

.myTabs {
    skin:ClassReference("skins.SolidFillTabSkin");
    fillAlpha:1;
    borderAlpha:1;
    cornerRadius:0;
    /*color:#FF0000;
    chromeColor: #00FF00;
    textRollOverColor: #0000FF;
    textSelectedColor: #00FF00;*/
    textFieldClass: ClassReference("mx.core.UIFTETextField"); /* default is mx.core.UITextField*/
}

textSelectedColor 几乎从不触发。所以从来没有选定的文本颜色。另外,选择选项卡时,文本会向下移动!

所以下面是一个新皮肤,其中的文字不会向下移动。如果您不想使用默认设置,您可能需要在皮肤中设置文本和填充颜色,尽管您可以使用 chromeColor 设置填充颜色。该皮肤支持cornerRadius和borderAlpha,

<?xml version="1.0" encoding="utf-8"?>

<!--
Apache License 2.0.
-->

<!--- The Spark skin class for the tabs of the MX TabNavigator container. 

How to use: 

With a specific TabNavigator:  

.myTabNavigator {
    tabStyleName: "myTabs";
}

.myTabs {
    skin:ClassReference("mySkins.MyTabSkin");
}

Applied to all Tabs in all TabNavigators:  

mx|Tab {
    skin:ClassReference("com.flexcapacitor.skins.SolidFillTabNavigatorButtonSkin");
    fillAlpha:1;
    borderAlpha:1;
    cornerRadius:0;
    /*color:#FF0000;
    chromeColor: #00FF00;
    textRollOverColor: #0000FF;
    textSelectedColor: #00FF00;*/
    textFieldClass: ClassReference("mx.core.UIFTETextField"); /* default is mx.core.UITextField*/
}

@see mx.containers.TabNavigator

@langversion 3.0
@playerversion Flash 10
@playerversion AIR 1.5
@productversion Flex 4
-->
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" 
             minWidth="21" minHeight="21"
             alpha.disabledStates="0.5">

    <fx:Script>
    <![CDATA[
        import mx.controls.Button;
        import mx.core.IUITextField;
        import mx.core.mx_internal;

        use namespace mx_internal;

        private var cornerRadius:Number = 0;

        /**
         * @private
         */
        override protected function initializationComplete():void
        {
            useChromeColor = true;
            super.initializationComplete();
        }

        /**
         * COPIED FROM TabBarButtonSkin.mxml
         *  @private
         *  The cornerRadius style is specified by the TabBar, not the button itself.   
         * 
         *  Rather than bind the corner radius properties of the s:Rect's in the markup 
         *  below to hostComponent.owner.getStyle("cornerRadius"), we reset them here, 
         *  each time a change in the value of the style is detected.  Note that each 
         *  corner radius property is explicitly initialized to the default value of 
         *  the style; the initial value of the private cornerRadius property.
         */
        private function updateCornerRadius():void
        {
            var cr:Number = getStyle("cornerRadius");
            if (cornerRadius != cr)
            {
                cornerRadius = cr;
                fill.topLeftRadiusX = cornerRadius;
                fill.topRightRadiusX = cornerRadius;
            }
        }

        /**
         * COPIED FROM TabBarButtonSkin.mxml
         *  @private
         *  This function creates the path data used by borderTop and selectedHighlight.
         */
        private function createPathData(isBorder:Boolean):String
        {
            var left:Number = 0;
            var right:Number = width;
            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;
        }

        /**
         * COPIED FROM TabBarButtonSkin.mxml
         *  @private
         *  The borderTop s:Path is just a s:Rect with the bottom edge left out.
         *  Given the rounded corners per the cornerRadius style, the result is 
         *  roughly an inverted U with the specified width, height, and cornerRadius.
         * 
         *  Circular arcs are drawn with two curves per flash.display.Graphics.GraphicsUtil.
         */        
        private function updateBorderTop(width:Number, height:Number):void
        {
            // Generate path data and lay it out. The path is not being layout by the default BasicLayout of this skin
            // since we excluded it from the layout.
            var path:String = createPathData(true);
            borderTop.data = path;
            borderTop.setLayoutBoundsSize(width, height, false);
            borderTop.setLayoutBoundsPosition(0, 0, false);
        }

        /**
         * @private
         */
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
        {
            updateCornerRadius();
            updateBorderTop(unscaledWidth, unscaledHeight);

            var button:Button = owner as Button;// is the Button
            var textField:IUITextField = button.getTextField();
            var label:String = button.label;

            // if you want to use your own text field hide the other one here
            if (textField) {
                //textField.includeInLayout = false;
                textField.visible = false;
            }

            if (labelDisplay) {
                labelDisplay.text = label;
            }

            var borderAlpha:Number = button.getStyle("borderAlpha");
            if (!isNaN(borderAlpha)) {
                borderTop.alpha = borderAlpha;
                if (lineAlongTheBottom) {
                    lineAlongTheBottom.alpha = borderAlpha;
                }
            }

            var fillAlpha:Number = button.getStyle("fillAlpha");
            if (!isNaN(fillAlpha)) {
                fillColor.alpha = fillAlpha;
            }

            var fillColorValue:Number = button.getStyle("fillColor");
            if (!isNaN(fillColorValue)) {
                //fillColor.color = fillColorValue;
            }

            super.updateDisplayList(unscaledWidth, unscaledHeight);

        }
        ]]>
    </fx:Script>

    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" stateGroups="disabledStates"/>
        <s:State name="selectedUp" stateGroups="selectedStates" />
        <s:State name="selectedOver" stateGroups="selectedStates" />
        <s:State name="selectedDown" stateGroups="selectedStates" />
        <s:State name="selectedDisabled" stateGroups="disabledStates, selectedStates" />
    </s:states>

    <!-- layer 1: fill -->
    <s:Rect id="fill" left="1" right="1" top="1" bottom="0" >
        <s:fill>
            <s:SolidColor id="fillColor" 
                          color="0xFFFFFF" 
                          color.selectedStates="0xECEEEE"
                          color.over="0xECEEEE" 
                          alpha="1" />
<!--            <s:LinearGradient rotation="90">
                <s:GradientEntry color="0xE4E4E4" color.over="0xCACACA"
                                 color.selectedStates="0xFFFFFF"
                                 alpha="1" />
                <s:GradientEntry color="0xA1A1A1" color.over="0x878787"
                                 color.selectedStates="0xE4E4E4" 
                                 alpha="1" />
            </s:LinearGradient>-->
        </s:fill>
    </s:Rect>


    <!-- layer 4: border - unselected only -->
    <s:Line id="lineAlongTheBottom" left="0" right="0" bottom="0" excludeFrom="selectedStates" >
        <s:stroke>
            <s:SolidColorStroke color="0x696969" alpha="1" />
        </s:stroke>
    </s:Line>

    <!--- Set includeInLayout="false" as we regenerate the path data and lay out the path in
    the updateDisplayList() override and we don't want it to affect measurement. @private
    -->
    <s:Path id="borderTop" left="0" right="0" top="0" bottom="0" includeInLayout="false">
        <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 -->
    <!--- @copy spark.components.supportClasses.ButtonBase#labelDisplay -->
    <s:Label id="labelDisplay"
             visible="true"
             textAlign="center"
             verticalAlign="middle"
             maxDisplayedLines="1" 
             horizontalCenter="0" verticalCenter="2"
             left="10" right="10" top="2" bottom="2">
    </s:Label>
</s:SparkSkin>