要求
必备知识
本文假设您熟悉 Flex 3 Framework。
用户级别
全部
必需产品
- Flex 4 (下载试用版)
范例文件
- flex4_skinning_sample2.zip (4642 KB)
Flex 4(代码名:Gumbo)发行版的重要主题之一是“在心中设计”,而外观设计是这一主题的一个重要组成部分。现今 Web 上可以找到的某些最具创意的作品正是由 Flash Player 创建而成。但是 Flex 应用程序因为外观十分相似而闻名,因为许多开发人员选用 Flex 的默认外观(称为 Halo)而不是采用其他样式或外观设计。
Flex 4 beta 使得彻底改变应用程序的外观变得更简单。新的外观设计架构以 Flex 4 中的其他更改为基础,它清晰划分了组件的逻辑元素和可视元素。因此,Flex 4 beta 中的组件都不包含可视外观的任何信息。所有这类信息都包含在外观文件中。借助 FXG 和新的状态语法,您可以使用 MXML 编写新的外观文件,令它们更容易读写,使用工具访问它们也更简单。
在本文中,您将了解到 Flex 4 beta 中外观设计架构的改进情况。通过为按钮编写一个基本外观,您将接触到一些 FXG 和新的状态语法。然后,您将通过滑块外观设计过程了解组件与外观相互交互所采用的合同。最后,您将新建一个适合外观设计的组件,深入钻研可设计外观的组件。
注意:本文中的术语 Halo 组件是指 Flex 3 原先包含的组件。术语 Spark 组件是指 Flex 4 beta 中的一套新组件。
编写简单的按钮外观
FXG 是一种针对矢量图形的声明性标记语言,旨在充分利用 Flash Player。借助新的标记,您可以轻松创建自定义按钮。对于这个按钮,我们从一个内含文本的简单矩形入手(请参阅图 1)。
Sample1.mxml
<?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"> <s:Group verticalCenter="0" horizontalCenter="0"> <s:Rect id="rect" radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0"> <s:fill> <s:SolidColor color="0x77CC22" /> </s:fill> <s:stroke> <s:SolidColorStroke color="0x131313" weight="2"/> </s:stroke> </s:Rect> <s:Label text="Button!" color="0x131313" textAlign="center" verticalAlign="middle" horizontalCenter="0" verticalCenter="1" left="12" right="12" top="6" bottom="6" /> </s:Group> </s:Application>

如果您熟悉 Flex 3,就熟悉以上语法,但您可能并不熟悉所使用的特定组件。Group 容器是 Spark 中基本的无边框容器。Rect
是一种新的 FXG 基本图形,正如您想到的那样-矩形。本文中的最后一个组件 SimpleText 是 Spark 中的一种新文本组件。MXML 读起来就像组件的描述-它是一个圆角的矩形,中间是一些采用 1 像素深灰笔触的文本,其余部分填充了绿色。
FXG 的妙处之一在于它不仅比编程绘制指令更容易理解,并且借助 XML 的结构,它更容易使用。有关 FXG 的更多信息,请参阅 FXG 规范。
将 Button 图形转换为 Button 外观
到目前为止,这个 MXML 文档还只是一个不包含交互的静态图稿。它还没有充分利用到 Flex 4 beta 中新的外观设计功能。因此,您需要将它与 Button 组件关联起来并将它用作外观。要创建一个 Spark 外观文件,请新建一个 MXML 文件并将 Skin
作为根标签。然后,将以上图形代码复制到其中:
ButtonSkin1.mxml
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled=".5"> <!-- states --> <s:states> <s:State name="up" /> <s:State name="over" /> <s:State name="down" /> <s:State name="disabled" /> </s:states> <!-- border and fill --> <s:Rect id="rect" radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0"> <s:fill> <s:SolidColor color="0x77CC22" /> </s:fill> <s:stroke> <s:SolidColorStroke color="0x131313" weight="2"/> </s:stroke> </s:Rect> <!-- text --> <s:Label text="Button!" color="0x131313" textAlign="center" verticalAlign="middle" horizontalCenter="0" verticalCenter="1" left="12" right="12" top="6" bottom="6" /> </s:Skin>
您会发现文件状态中还增加了一项。我稍后会讨论这一点。
完成外观文件后,您需要将它与 Button 组件关联起来。Spark 架构中每个可设计外观的组件都通过 skinClass
CSS 样式与一个外观关联在一起,可以通过样式表设置或通过 MXML 内联这个样式。在本例中,我将使用第二种方法:
Sample2.mxml
<?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"> <s:Button verticalCenter="0" horizontalCenter="0" skinClass="ButtonSkin1" click="trace('I\'ve been clicked!')" focusIn="trace('focus...on me?')" /> </s:Application>

现在您已经将按钮与一个新的外观文件关联在一起。Button 组件包含该按钮的所有行为逻辑。它将添加事件侦听函数、调度新事件、弄清组件所处的状态等等。外观不必处理上述任何事务,只需定义组件的可视部分。
但是,Button 与您一开始创建的静态图形看上去并无二致。按钮具有交互性,但它看上去并不是这样。这是因为您还没有定义按钮在不同状态下的外观。
介绍外观设计合同
静态外观令人乏味。要增加点乐趣,外观必须能够与组件相互交互。外观设计合同定义了两种元素,它们会相互交互。元素包含三个部分:外观状态、数据和部件(请参阅图 3)。一方面,组件定义了这三个不同部分,另一方面,外观对它们做出响应。

定义外观状态
Spark 中每个可设计外观的组件都有一组外观状态。您可以根据组件所处的外观状态更改外观的显示。Button 有四种基本外观状态:up
、over
、down
和 disabled
。您可以修改外观,使每个状态下呈现不同的外观(请参阅图 4)。
ButtonSkin2.mxml
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled=".5"> <!-- states --> <s:states> <s:State name="up" /> <s:State name="over" /> <s:State name="down" /> <s:State name="disabled" /> </s:states> <!-- dropshadow for the down state only --> <s:Rect radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0" includeIn="down"> <s:fill> <s:SolidColor color="0"/> </s:fill> <s:filters> <s:DropShadowFilter knockout="true" blurX="5" blurY="5" alpha="0.32" distance="2" /> </s:filters> </s:Rect> <!-- border and fill --> <s:Rect id="rect" radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0"> <s:fill> <s:SolidColor color="0x77CC22" color.over="0x92D64E" color.down="0x67A41D"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0x131313" weight="2"/> </s:stroke> </s:Rect> <!-- highlight on top --> <s:Rect radiusX="4" radiusY="4" top="2" right="2" left="2" height="50%"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="0xFFFFFF" alpha=".5"/> <s:GradientEntry color="0xFFFFFF" alpha=".1"/> </s:LinearGradient> </s:fill> </s:Rect> <!-- text --> <s:Label text="Button!" color="0x131313" textAlign="center" verticalAlign="middle" horizontalCenter="0" verticalCenter="1" left="12" right="12" top="6" bottom="6" /> </s:Skin>
ButtonSkin2.mxml

根据组件所处的外观状态,它会按照您对外观的定义方式呈现不同的外观。外观文件将充分利用新的状态点语法。这是 Flex 4 beta 的一个新增功能,它令编写状态修改内容更清晰、简洁。语法如下:property.stateName="该属性在该状态中的值"
。例如,alpha.disabled=".5"
表示当按钮转入 disabled
外观状态,外观将 Alpha 更改为 50%。在 over
和 down
状态中,我使用 color.over="0x92D64E" color.down="0x67A41D"
定义了一个不同的填充色。
对于新的状态语法,每个 MXML 组件包含一个 includeIn
和 excludeFrom
属性。只有 down
状态包含按钮外观中的投影,使它的按下效果相当不错。为了使外观更生动,我还添加了另一个矩形,旨在突出按钮在所有状态中的上部。
注意:有关 Flex 4 中得到增强的状态语法的更多信息,请参阅规范*。
使用按钮是一种交互体验,因为按钮会根据自己的外观状态更改可视外观。但是,您将发现一个问题,即组件的文本被硬编码到“Button!”。在下一部分中,您将了解到如何通过关联外观来显示组件的数据,即本例中 Button 的 label
属性。
获取组件的数据
建议您始终将 HostComponent
元数据放在外观中。HostComponent
元数据指向您要设计外观的组件,并且它是从外观访问组件必不可少的。定义后,您的外观包含一个 hostComponent
属性,该属性又指向组件。在您的 Button 外观中,您可以使用这个 hostComponent
属性绑定到按钮的 label 属性。
ButtonSkin3.mxml:
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled=".5"> <fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata> ... <!-- text --> <s:Label text="{hostComponent.label}" color="0x131313" textAlign="center" verticalAlign="middle" horizontalCenter="0" verticalCenter="1" left="12" right="12" top="6" bottom="6" /> </s:Skin>
声明按钮后,外观中的文本将以 label
属性为基础。
Sample4.mxml:
<?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"> <fx:Style> @namespace s "library://ns.adobe.com/flex/spark"; s|Button { skinClass: ClassReference("ButtonSkin3"); } </fx:Style> <s:layout> <s:VerticalLayout /> </s:layout> <s:Button label="Button #1" /> <s:Button label="Button #2" /> <s:Button label="Button #3" /> </s:Application>
主应用程序声明三个按钮。由于定义的 CSS 类型选择器,每个按钮使用相同的外观文件 ButtonSkin3
。但是,每个按钮的标签不同。由于外观现在通过拉入 label
属性来显示文本,按钮会像您期望的那样显示不同文本(请参阅图 5)。

您已看到外观设计合同包含的三个部分中的两个,即状态和数据。组件通过外观状态这个途径推动交互,而外观则定义了组件在这些状态中的外观。数据,作为可以由用户设置的组件属性,可以通过使用 HostComponent
元数据和 hostComponent
属性拉入外观中。在上例中,外观从 Button 组件拉入数据(label
属性)。关联数据的另一个方法是使用外观部分机制,将数据推入外观部件中。
外观设计合同(续):外观部件
外观部件构成了外观设计合同的第三个部分。Spark 中每个可设计外观的组件都有一组可以帮助您定义组件的外观部件。滚动条包含四个外观部件:增加按钮、减少按钮、轨道和缩览图。而 Button 只包含一个外观部件,即 labelElement。这是 Button 组件需要的部件。在上述 Button 外观中,如果您为文本组件提供 ID labelElement
,则无需将文本绑定到 {hostComponent.label}
,Button 即可识别这个外观部件并将 label 属性向下推入外观中。
ButtonSkin4.mxml:
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" alpha.disabled=".5"> <fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata> <!-- states --> <s:states> <s:State name="up" /> <s:State name="over" /> <s:State name="down" /> <s:State name="disabled" /> </s:states> <!-- dropshadow for the down state only --> <s:Rect radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0" includeIn="down"> <s:fill> <s:SolidColor color="0"/> </s:fill> <s:filters> <s:DropShadowFilter knockout="true" blurX="5" blurY="5" alpha="0.32" distance="2" /> </s:filters> </s:Rect> <!-- border and fill --> <s:Rect id="rect" radiusX="4" radiusY="4" top="0" right="0" bottom="0" left="0"> <s:fill> <s:SolidColor color="0x77CC22" color.over="0x92D64E" color.down="0x67A41D"/> </s:fill> <s:stroke> <s:SolidColorStroke color="0x131313" weight="2"/> </s:stroke> </s:Rect> <!-- highlight on top --> <s:Rect radiusX="4" radiusY="4" top="2" right="2" left="2" height="50%"> <s:fill> <s:LinearGradient rotation="90"> <s:GradientEntry color="0xFFFFFF" alpha=".5"/> <s:GradientEntry color="0xFFFFFF" alpha=".1"/> </s:LinearGradient> </s:fill> </s:Rect> <!-- text --> <s:Label id="labelDisplay" color="0x131313" textAlign="center" verticalAlign="middle" horizontalCenter="0" verticalCenter="1" left="12" right="12" top="6" bottom="6" /> <!-- transitions --> <s:transitions> <s:Transition> <s:CrossFade target="{rect}" /> </s:Transition> </s:transitions> </s:Skin>
SimpleText
不再绑定到主机组件。我为它提供了 ID labelElement
,该 ID 是 Button 需要的一个部件。Button 组件将为您关联数据,并将其 label
属性推入 labelElement
中。
除了分配标签元素外观部件,我还为外观添加了一个简单的 CrossFade
过渡。组件的所有可视部分都通过外观文件进行定义,其中包括过渡。在本例中,每当按钮更改状态时,状态之间都可以呈现出顺畅的淡化过渡效果。
为滑块设计外观
外观部件不仅可用于将组件的数据推入外观中,组件还可以使用它们关联行为。要弄清这一点,可以考虑滑块控件。滑块的两大部件是轨道和缩览图。在本例中,组件不会将任何数据推入要显示的外观部件中,而是将事件侦听函数添加到这些部件中,并根据组件的 value
属性为缩览图安排位置。例如,单击轨道时,组件将更新自己的 value
属性并将缩览图放在适当的位置。此外,还有一个动态外观部件 dataTip,当拖动缩览图以显示弹出信息时将使用它。图 6 中的示例是一个经过修改的简单滑块。

要构建出这种效果,您的外观文件必须声明三个外观部件:缩览图、轨道和 dataTip。
MySliderSkin.mxml
<?xml version="1.0" encoding="utf-8"?> <s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" minWidth="11" minHeight="100" alpha.disabled="0.5"> <fx:Metadata> [HostComponent("spark.components.VSlider")] </fx:Metadata> <s:states> <s:State name="normal" /> <s:State name="disabled" /> </s:states> <fx:Declarations> <fx:Component id="dataTip"> <s:DataRenderer minHeight="24" minWidth="40" x="20"> <s:Rect top="0" left="0" right="0" bottom="0"> <s:fill> <s:SolidColor color="0xFFF46B" alpha=".9"/> </s:fill> <s:filters> <s:DropShadowFilter angle="90" color="0x999999" distance="3"/> </s:filters> </s:Rect> <s:Label id="labelField" text="{data}" horizontalCenter="0" verticalCenter="1" left="5" right="5" top="5" bottom="5" textAlign="center" verticalAlign="middle" color="0x555555" /> </s:DataRenderer> </fx:Component> </fx:Declarations> <s:Button id="track" left="5" right="5" top="0" bottom="0" skinClass="MyTrackSkin" /> <s:Button id="thumb" left="0" right="0" width="18" height="8" skinClass="MyThumbSkin" /> </s:Skin>
在外观中定义组件后,组件负责将这些外观部件关联在一起。它将事件侦听函数添加到缩览图,以便您在轨道上拖动缩览图。它还根据值放置缩览图。我们可以在范例源代码中找到 MyTrackSkin
和 MyThumbSkin
。您可以在其中看到更多栩栩如生的 FXG 示例。请注意,定义的缩览图外观在形状上与默认 Spark 外观缩览图截然不同。
dataTip 外观部件是动态的-组件负责创建这个部件并安排位置。在本例中,当您拖动缩览图时,dataTip 会弹出并显示在缩览图右侧。由于外观设计合同,外观可以定义外观部件及其可视部分,而不必担心任何行为关联到这些部件。所有逻辑关联操作在组件中进行。
注意:Flex 4 beta 中的许多内建组件使用外观部件将行为关联到部件,还将数据向下推入外观部件中。获取外观中的数据的另一个方法是通过 hostComponent
属性拉入它们。
为组件创建外观时,并非所有外观部件都是必需的。例如,VSlider 的 dataTip 外观部件就不是必需的。如果它不存在,则不会显示数据提示。
创建可设计外观的组件
可设计外观的 Spark 组件在幕后并没有什么特殊举动。它们拥有数据属性并通过元数据宣传它们所需的外观部件和外观状态。它们还关联几个主要方法,用于管理外观和外观部件的生命周期。您可以轻松新建一个可设计外观的相同组件。
为了证明这一点,您可以创建一个简单的 NoteCard 组件,它可用于在屏幕上显示留言。在图 7 的示例中,应用程序创建了多张写有随机引言的留言。

主应用程序创建了一个包含随机引言的 NoteCard 并将它略作旋转。这个应用程序的有趣之处在于 NoteCard 类,它扩展了 spark.components.supportClasses.SkinnableComponent 类并将它关联到外观设计生命周期方法。
NoteCard.as:
package { [SkinState("normal")] [SkinState("disabled")] public class NoteCard extends SkinnableComponent { public function NoteCard() { super(); } [SkinPart(required="true")] public var labelDisplay:TextBase; [SkinPart(required="false")] public var closeButton:Button; private var _text:String; public function get text():String { return _text; } public function set text(value:String):void { if (_text == value) return; _text = value; } ... } }
这个组件声明了数据属性、外观状态和外观部件。在数据方面,NoteCard 有一个公共 text
属性。NoteCard 还有两个外观状态 normal
和 disabled
,它们使用类顶部的 SkinStates
元数据进行声明。它告诉外观需要实施这两个状态。
NoteCard 还有两个通过 SkinPart
元数据声明的外观部件。SkinPart
元数据就在外观部件名称的上方。在本例中,labelElement 是必需的 TextGraphicElement
外观部件,而 closeButton 则是可选的 Button
外观部件。
由于外观是在运行时加载的,所以第一次启动组件时,无法确保您获得外观。也无法确保您获得所有外观部件,尤其当它们是可选部件时。框架负责将外观中声明的部件关联到组件属性定义,并通过外观设计生命周期方法通知组件它们已经准备就绪。
实施组件上的外观状态
要关联外观状态,您需要覆盖 getCurrentSkinState
以返回外观现在应当处于的状态。在本例中,它将返回 "normal"
或 "disabled"
。当某个事件导致外观状态失效时,组件应调用 invalidateSkinState
。
NoteCard.as
package { [SkinState("normal")] [SkinState("disabled")] public class NoteCard extends SkinnableComponent { ... override public function set enabled(value:Boolean) : void { if (enabled != value) invalidateSkinState(); super.enabled = value; } override protected function getCurrentSkinState() : String { if (!enabled) return "disabled"; return "normal" } ... } }
设置 enabled
属性后,setter 调用 invalidateSkinState
。此操作将通知外观需要更改其状态并调用 getCurrentSkinState
。
将外观部件关联到组件
要关联外观部件,您应当覆盖两个主要方法:partAdded
和 partRemoved
。这些方法将告诉您添加和删除特定外观部件的时间。加载外观时可以添加或删除部件;在运行时交换外观;某个部件上线,因为它被延迟并且可能只存在于某些状态中,或是新建了一个动态部件。添加部件时,您应当把握机会,将所需的任何数据向下推入其中并关联起任何事件侦听函数。删除部件时,您应当执行相反操作。
NoteCard.as
package { public class NoteCard extends SkinnableComponent { [SkinPart(required="true")] public var labelDisplay:TextBase; [SkinPart(required="false")] public var closeButton:Button; public function set text(value:String):void { if (_text == value) return; _text = value; if (labelDisplay) labelDisplay.text = value; } override protected function partAdded(partName:String, instance:Object) : void { super.partAdded(partName, instance); if (instance == labelDisplay) labelDisplay.text = _text; if (instance == closeButton) closeButton.addEventListener(MouseEvent.CLICK, closeButton_clickHandler); } override protected function partRemoved(partName:String, instance:Object) : void { super.partRemoved(partName, instance); if (instance == closeButton) closeButton.removeEventListener(MouseEvent.CLICK, closeButton_clickHandler); } protected function closeButton_clickHandler(event:MouseEvent) : void { event.stopPropagation(); IVisualElementContainer(parent).removeElement(this); } } }
在 partAdded
中,当关联起 labelElement 时,我将 text
属性推入这个外观部件中。我还在 text
setter 中检查 labelElement 是否存在并关联-如果是,我将确保 labelElement 的 text
属性与组件保持同步。在 partAdded
中,我为 closeButton 外观部件添加一个单击事件侦听函数。在 partRemoved
中,我确保删除了相同的单击事件侦听函数。
对于 SkinnableComponent
,这是您参与这种强大的外观设计机制所需的全部操作。当他人为这个组件创建外观时,他们需要实施外观状态并关联到外观部件,才能获得所需的行为。范例源文件中可以找到图 6 中的黑板外观,虽然这是一个简单的组件定义,您却可以使用不同的外观彻底改变它的外观。这是外观设计的实力所在。
注意:创建可设计外观的组件时,您可能必须决定特定行为属于组件还是外观。没有清晰且必须遵循的强硬路线。只要可以令您的工作变得更轻松就行。但是总体指导方针是,定义组件外观的所有项应放入使用 MXML 声明的外观文件中。另一方面,如果多个外观需要某个行为,将该行为放入可设计外观的组件中可能是个好主意。例如,滑块中缩览图的放置是在 VSlider 和 HSlider 中而不是在外观中完成的。
后续工作
Flex 4 beta 中的外观设计经历了一番重大修改。组件与其外观之间界限分明。组件包含组件的数据、行为及核心逻辑,而外观则定义组件的外观。组件使用 ActionScript 编写而成,外观则使用 MXML,如果没有 FXG 和新的状态语法,这一切不可能发生。组件与外观通过外观设计合同相互沟通。由于它们是彼此独立的文件,所以可以轻松换入新的外观,从而彻底改变组件的外观。
有关 Flex 4 beta 中的外观设计的更多信息,请参阅外观设计架构规范和 Gumbo 组件架构白皮书。