注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

无线时代辐射无穷

抓紧生宝宝,小心辐射

 
 
 

日志

 
 

Flex4组件教程:自定义两级导航菜单组件  

2011-06-18 19:25:38|  分类: flex |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

原文链接
对于两级导航菜单,我们应该不会陌生,很多网站都使用了这个效果,如下面这张图片所示,当鼠标划过或点击一级菜单,会出现相应的二级菜单,也就是我们经常说的菜单联动效果。

Flex4组件教程:自定义两级导航菜单组件 - wolfgangkiefer - 南海群岛

当然图片中的这个效果是基于HTML和JavaScript实现的,那么在Flex开发中,我们能否达到这样的效果呢?当然是可以的,下面我们就来探讨一下实现步骤。

实现思路

在动手写代码之前,先别急,考虑一下如何实现。最先要考虑的,是Flex中是否已经提供了现成的两级菜单组件?如果有的话,我们直接拿来用就是,没必要自己重复造轮子了,但很可惜,Flex本身没有提供这样的组件。不过有一个和我们想实现的很类似的组件,就是ButtonBar。ButtonBar默认就支持对内部导航按钮进行排版,并维持选中状态。如下图所示:

Flex4组件教程:自定义两级导航菜单组件 - wolfgangkiefer - 南海群岛

但它默认只有一级结构,所以我们需要扩展一下ButtonBar,实现我们想要的二级结构。完成结果如下图所示:

Flex4组件教程:自定义两级导航菜单组件 - wolfgangkiefer - 南海群岛

点击这里查看编译后的SWF文件

准备工作

首先准备好定制皮肤所需的位图文件,当然您也可以用MXML图形直接在皮肤里绘制,但如果图形过于复杂的话,对于运行时性能会有影响,如果是这种情况还是用合并图层后的图片来处理比较合适。

因为是开发Flex组件,工具建议使用Flash Builder。

另外您需要了解为Spark组件定制皮肤的基本知识,下面是一些参考文章或视频:

实现过程

首先准备好数据源,采用ArrayList:

  1. [Bindable]
  2. private var menuData:ArrayList = new ArrayList([
  3. {id:1, name:"首页"},
  4. {id:2, name:"管理",subMenu:new ArrayList([
  5. {id:21, name:"管理"}
  6. ])},
  7. {id:3, name:"导航",subMenu:new ArrayList([
  8. {id:21, name:"导航1"},
  9. {id:22, name:"导航2"},
  10. {id:22, name:"导航3"}
  11. ])},
  12. {id:4, name:"吃饭"},
  13. {id:4, name:"睡觉"},
  14. {id:3, name:"打豆豆",subMenu:new ArrayList([
  15. {id:21, name:"豆豆 1"}
  16. ])},
  17. {id:4, name:"退出"},
  18. ]);

然后创建自定义组件MenuSlider,里面放两个ButtonBar,一个是一级菜单,一个是二级菜单,为了提升用户体验,我们加了一些过渡动画,也就是其中Transition部分的定义。并且我们创建了两个ArrayList,分别为一级菜单和二级菜单供应数据,二级菜单的数据是从一级菜单中的选项中获取的。

您可能已经注意到,对于二级菜单,因为需要使用一些自定义属性,所以我们扩展了ButtonBar,但只是加了属性,并无其它逻辑修改。

MenuSlider.mxml

  1. <s:SkinnableContainer xmlns:fx="http://ns.adobe.com/mxml/2009"
  2. xmlns:s="library://ns.adobe.com/flex/spark"
  3. xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="65" xmlns:comp="comp.*">
  4. <fx:Metadata>
  5. [Event(name="change",type="flash.events.Event")]
  6. </fx:Metadata>
  7. <fx:Script>
  8. <![CDATA[
  9. import mx.collections.ArrayList;
  10.  
  11. import spark.events.IndexChangeEvent;
  12. [Bindable]
  13. /**一级数据源*/
  14. public var dataProvider:ArrayList;
  15. [Bindable]
  16. /**二级数据源*/
  17. public var subDataProvider:ArrayList;
  18. /**当前选中项,可以是一级或二级中的项*/
  19. public var selectedItem:Object;
  20.  
  21. [Bindable]
  22. private var prevDataNotNull:Boolean;
  23.  
  24. protected function mainButtonBarChangeHandler(event:IndexChangeEvent):void{
  25. currentState = "normal";
  26. selectedItem = mainButtonBar.selectedItem;
  27. if(selectedItem==null) return;
  28. if(mainButtonBar.selectedItem.subMenu != null) {
  29. subDataProvider = null;
  30. currentState = "sub";
  31. prevDataNotNull = true;
  32. } else {
  33. prevDataNotNull = false;
  34. }
  35. dispatchEvent(new Event("change"));
  36. }
  37.  
  38. protected function validateSubMenu():void {
  39. autoLayout=true;
  40. if(currentState == "sub") {
  41. subButtonBar.y = 35;
  42. subDataProvider = mainButtonBar.selectedItem.subMenu;
  43. } else {
  44. subButtonBar.y = 0;
  45. subDataProvider = null;
  46. }
  47. }
  48.  
  49. private function subButtonBarChangeHandler(event:IndexChangeEvent):void {
  50. selectedItem = subButtonBar.selectedItem;
  51. if(selectedItem == null) return;
  52. dispatchEvent(new Event("change"));
  53. }
  54.  
  55. ]]>
  56. </fx:Script>
  57.  
  58. <s:states>
  59. <s:State name="normal" />
  60. <s:State name="sub" />
  61. </s:states>
  62.  
  63. <s:transitions>
  64. <s:Transition fromState="normal" toState="sub">
  65. <s:Sequence target="{subButtonBar}" effectStart="autoLayout=false" effectEnd="validateSubMenu()">
  66. <s:Move yFrom="35" yTo="0" duration="{prevDataNotNull?500:0}"/>
  67. <s:Move yFrom="0" yTo="35"/>
  68. </s:Sequence>
  69. </s:Transition>
  70. <s:Transition fromState="sub" toState="normal">
  71. <s:Move target="{subButtonBar}" yTo="0" effectStart="autoLayout=false" effectEnd="validateSubMenu()"/>
  72. </s:Transition>
  73. </s:transitions>
  74.  
  75. <comp:SubButtonBar id="subButtonBar"
  76. width="100%" height="30" styleName="subButtonBar"
  77. dataProvider="{subDataProvider}" labelField="name"
  78. mainDataProvider="{dataProvider}"
  79. mainSelectedIndex="{mainButtonBar.selectedIndex}"
  80. change="subButtonBarChangeHandler(event)"
  81. />
  82. <s:ButtonBar id="mainButtonBar"
  83. width="100%" height="35" styleName="mainButtonBar"
  84. dataProvider="{dataProvider}" labelField="name"
  85. change="mainButtonBarChangeHandler(event)"
  86. />
  87.  
  88. </s:SkinnableContainer>

SubButtonBar.as

  1. public class SubButtonBar extends ButtonBar
  2. {
  3. [Bindable]
  4. public var mainDataProvider:IList;
  5.  
  6. [Bindable]
  7. public var mainSelectedIndex:int;
  8.  
  9. public function SubButtonBar()
  10. {
  11. super();
  12. }
  13. }

因为ButtonBar是可以定制外观的,所以我们为MainButtonBar和SubButtonBar分别定义了外观:

MainButtonBar的外观定义:

  1. <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
  2. alpha.disabled="0.5">
  3.  
  4. <fx:Metadata>
  5. <![CDATA[
  6. /**
  7.   * @copy spark.skins.spark.ApplicationSkin#hostComponent
  8.   */
  9. [HostComponent("spark.components.ButtonBar")]
  10. ]]>
  11. </fx:Metadata>
  12.  
  13. <s:states>
  14. <s:State name="normal" />
  15. <s:State name="disabled" />
  16. </s:states>
  17.  
  18. <fx:Declarations>
  19. <!---
  20. @copy spark.components.ButtonBar#firstButton
  21. @default spark.skins.spark.ButtonBarFirstButtonSkin
  22. @see spark.skins.spark.ButtonBarFirstButtonSkin
  23. -->
  24. <fx:Component id="firstButton">
  25. <s:ButtonBarButton styleName="mainSelectButton" />
  26. </fx:Component>
  27.  
  28. <!---
  29. @copy spark.components.ButtonBar#middleButton
  30. @default spark.skins.spark.ButtonBarMiddleButtonSkin
  31. @see spark.skins.spark.ButtonBarMiddleButtonSkin
  32. -->
  33. <fx:Component id="middleButton" >
  34. <s:ButtonBarButton styleName="mainSelectButton" />
  35. </fx:Component>
  36.  
  37. <!---
  38. @copy spark.components.ButtonBar#lastButton
  39. @default spark.skins.spark.ButtonBarLastButtonSkin
  40. @see spark.skins.spark.ButtonBarLastButtonSkin
  41. -->
  42. <fx:Component id="lastButton" >
  43. <s:ButtonBarButton styleName="mainSelectButton" />
  44. </fx:Component>
  45.  
  46. </fx:Declarations>
  47.  
  48. <s:Rect id="bgFill" width="100%" height="100%">
  49. <s:fill>
  50. <s:SolidColor color="#000000" />
  51. </s:fill>
  52. </s:Rect>
  53.  
  54. <!--- @copy spark.components.SkinnableDataContainer#dataGroup -->
  55. <s:DataGroup id="dataGroup" width="100%" height="100%">
  56. <s:layout>
  57. <s:ButtonBarHorizontalLayout gap="0"/>
  58. </s:layout>
  59. </s:DataGroup>
  60.  
  61. </s:Skin>

注意外观部分我们没有做什么改变,只是将内部所需的ButtonBarButton的皮肤做了变更(参见源码中的CSS部分的定义,这里不再阐述)

SubButtonBar的皮肤定义:

  1. <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
  2. alpha.disabled="0.5" xmlns:layout="layout.*">
  3.  
  4. <fx:Metadata>
  5. <![CDATA[
  6. /**
  7.   * @copy spark.skins.spark.ApplicationSkin#hostComponent
  8.   */
  9. [HostComponent("comp.SubButtonBar")]
  10. ]]>
  11. </fx:Metadata>
  12.  
  13. <s:states>
  14. <s:State name="normal" />
  15. <s:State name="disabled" />
  16. </s:states>
  17.  
  18. <fx:Script>
  19. <![CDATA[
  20. [Bindable]
  21. private var normalBgImage:Class;
  22. override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
  23. normalBgImage=getStyle("icon");
  24. super.updateDisplayList(unscaledWidth,unscaledHeight);
  25. }
  26. ]]>
  27. </fx:Script>
  28.  
  29. <fx:Declarations>
  30. <!---
  31. @copy spark.components.ButtonBar#firstButton
  32. @default spark.skins.spark.ButtonBarFirstButtonSkin
  33. @see spark.skins.spark.ButtonBarFirstButtonSkin
  34. -->
  35. <fx:Component id="firstButton">
  36. <s:ButtonBarButton styleName="subSelectButton" />
  37. </fx:Component>
  38.  
  39. <!---
  40. @copy spark.components.ButtonBar#middleButton
  41. @default spark.skins.spark.ButtonBarMiddleButtonSkin
  42. @see spark.skins.spark.ButtonBarMiddleButtonSkin
  43. -->
  44. <fx:Component id="middleButton" >
  45. <s:ButtonBarButton styleName="subSelectButton" />
  46. </fx:Component>
  47.  
  48. <!---
  49. @copy spark.components.ButtonBar#lastButton
  50. @default spark.skins.spark.ButtonBarLastButtonSkin
  51. @see spark.skins.spark.ButtonBarLastButtonSkin
  52. -->
  53. <fx:Component id="lastButton" >
  54. <s:ButtonBarButton styleName="subSelectButton" />
  55. </fx:Component>
  56.  
  57. </fx:Declarations>
  58.  
  59. <s:BitmapImage id="bgFill" width="100%" height="100%" source="{normalBgImage}" smooth="true"/>
  60.  
  61. <!--- @copy spark.components.SkinnableDataContainer#dataGroup -->
  62. <s:DataGroup id="dataGroup" width="100%" height="100%">
  63. <s:layout>
  64. <layout:SubMenuBarLayout
  65. mainDataProvider="{hostComponent.mainDataProvider}"
  66. mainSelectedIndex="{hostComponent.mainSelectedIndex}"
  67. gap="0"/>
  68. </s:layout>
  69. </s:DataGroup>
  70.  
  71. </s:Skin>

注意我们除了替换内部按钮的样式,也将SubButtonBar的背景换成了一张图片,图片的嵌入定义参见CSS部分的定义。

对于ButtonBar内部的每个按钮,我们将外观改成了由嵌入图片组成的实现机制(注意bgImage的定义):

  1. <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
  2. xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minWidth="21" minHeight="21" alpha.disabledStates="0.5">
  3. <fx:Metadata>[HostComponent("spark.components.ButtonBarButton")]</fx:Metadata>
  4.  
  5. <!-- host component -->
  6. <fx:Script fb:purpose="styling">
  7. /* Define the skin elements that should not be colorized.
  8.   For toggle button, the graphics are colorized but the label is not. */
  9. static private const exclusions:Array = ["labelDisplay"];
  10.  
  11. [Bindable]
  12. private var normalBgImage:Class;
  13. [Bindable]
  14. private var downBgImage:Class;
  15. [Bindable]
  16. private var overBgImage:Class;
  17. [Bindable]
  18. private var disableBgImage:Class;
  19. [Bindable]
  20. private var selectedBgImage:Class;
  21.  
  22. /**
  23.   * @private
  24.   */
  25. override public function get colorizeExclusions():Array {return exclusions;}
  26.  
  27. /**
  28.   * @private
  29.   */
  30. override protected function initializationComplete():void
  31. {
  32. useChromeColor = true;
  33. super.initializationComplete();
  34. this.buttonMode = true;
  35. this.mouseChildren = false;
  36. }
  37.  
  38. /**
  39.   * @private
  40.   */
  41. override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number) : void
  42. {
  43. normalBgImage=getStyle("icon");
  44. downBgImage=getStyle("downIcon");
  45. overBgImage=getStyle("overIcon");
  46. disableBgImage=getStyle("disableIcon");
  47. selectedBgImage=getStyle("selectedIcon");
  48.  
  49. super.updateDisplayList(unscaledWidth, unscaledHeight);
  50. }
  51.  
  52. private var cornerRadius:Number = 2;
  53. </fx:Script>
  54.  
  55. <!-- states -->
  56. <s:states>
  57. <s:State name="up" />
  58. <s:State name="over" stateGroups="overStates" />
  59. <s:State name="down" stateGroups="downStates" />
  60. <s:State name="disabled" stateGroups="disabledStates" />
  61. <s:State name="upAndSelected" stateGroups="selectedStates, selectedUpStates" />
  62. <s:State name="overAndSelected" stateGroups="overStates, selectedStates" />
  63. <s:State name="downAndSelected" stateGroups="downStates, selectedStates" />
  64. <s:State name="disabledAndSelected" stateGroups="selectedUpStates, disabledStates, selectedStates" />
  65. </s:states>
  66.  
  67. <s:transitions>
  68. <s:Transition fromState="*" toState="over">
  69. <s:Fade target="{bgImage}" alphaFrom="0.6" alphaTo="1" />
  70. </s:Transition>
  71. </s:transitions>
  72.  
  73. <s:BitmapImage id="bgImage"
  74. width="100%" height="100%"
  75. source.up="{normalBgImage}"
  76. source.over="{overBgImage}"
  77. source.down="{downBgImage}"
  78. source.disabled="{disableBgImage}"
  79. source.upAndSelected="{selectedBgImage}"
  80. source.overAndSelected="{selectedBgImage}"
  81. source.downAndSelected="{selectedBgImage}"
  82. source.disabledAndSelected="{disableBgImage}"
  83. />
  84.  
  85. <!-- layer 8: text -->
  86. <!--- @copy spark.components.supportClasses.ButtonBase#labelDisplay -->
  87. <s:Label id="labelDisplay"
  88. textAlign="center"
  89. verticalAlign="middle"
  90. maxDisplayedLines="1"
  91. horizontalCenter="0" verticalCenter="1"
  92. left="10" right="10" top="2" bottom="2">
  93. </s:Label>
  94.  
  95. </s:SparkSkin>

最后我们发现,SubButtonBar的布局处理方式跟MainButtonBar是不一样的,它需要将按钮与MainButtonBar中的选中按钮的位置有对齐关系,这样看起来更美观一些。得益于Spark容器外观与布局的分离,我们可以扩展出一个布局类来实现这个功能:

  1. public class SubMenuBarLayout extends ButtonBarHorizontalLayout
  2. {
  3. [Bindable]
  4. public var mainDataProvider:IList;
  5.  
  6. [Bindable]
  7. public var mainSelectedIndex:int;
  8.  
  9. private var itemWidth:Number;
  10. private var mainMenuItemX:Number;
  11.  
  12. public function SubMenuBarLayout()
  13. {
  14. super();
  15. }
  16.  
  17. override public function updateDisplayList(width:Number, height:Number):void
  18. {
  19. var layoutTarget:GroupBase = target;
  20. if (!layoutTarget) return;
  21. itemWidth = width/mainDataProvider.length;
  22. var count:int = layoutTarget.numElements;
  23. var layoutElement:ILayoutElement;
  24. if(itemWidth*(count+1) >= width) {
  25. super.updateDisplayList(width, height);
  26. } else {
  27. // Resize and position children
  28. var i:int = 0;
  29. var x:Number = 0;
  30. var paddingLeft:Number = itemWidth;
  31. mainMenuItemX = itemWidth*mainSelectedIndex;
  32. if(paddingLeft+itemWidth*count < mainMenuItemX) {
  33. paddingLeft = mainMenuItemX-itemWidth*(count-1)+gap;
  34. }
  35. x+=paddingLeft;
  36. for (i = 0; i < count; i++)
  37. {
  38. layoutElement = layoutTarget.getElementAt(i);
  39. if (!layoutElement || !layoutElement.includeInLayout)
  40. continue;
  41. layoutElement.setLayoutBoundsSize(itemWidth, height);
  42. layoutElement.setLayoutBoundsPosition(x, 0);
  43. x += gap + layoutElement.getLayoutBoundsWidth();
  44. }
  45. }
  46. }
  47.  
  48. }

这个自定义的布局类在SubButtonBar的皮肤中得到了应用:

  1. <s:DataGroup id="dataGroup" width="100%" height="100%">
  2. <s:layout>
  3. <layout:SubMenuBarLayout
  4. mainDataProvider="{hostComponent.mainDataProvider}"
  5. mainSelectedIndex="{hostComponent.mainSelectedIndex}"
  6. gap="0"/>
  7. </s:layout>
  8. </s:DataGroup>

由于时间仓促,关于CSS中的定义步骤以及和Skin的整合,不在本文阐述了,请读者自行参阅源码和RIAMeeting的相关视频和教程:)

源码下载

http://www.riameeting.com/examples/button_bar/srcview/

  评论这张
 
阅读(601)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017