英文原文: http://www.adobe.com/devnet/flex/articles/flex4_skinning.html
本文翻译原创链接: http://www.smithfox.com/?e=34 转载请注明
翻译: smithfox
上接:
http://smithfox.iteye.com/admin/blogs/847465
制作slider皮肤
皮肤parts不仅可以推送组件数据到皮肤中,组件也可以用它们来注册行为。为了讲得更明白,以slider组件为例。slider两个主要 parts是轨迹条和滑动块。在这个例子中,该组件没有把任何数据推送到皮肤来显示,但它添加了事件监听器到parts中并且会根据组件的value属性 执行滑动块的布局。例如,当点击轨迹条,组件会更新其value属性并且定位滑动块到适当位置。此外,还有动态皮肤parts,数据提示,这是用来拖动滑 块时显示弹出的提示信息。 在图6所示是一个简单slider和一个修改后的slider。
图6: 修改后的slider(左边) 和原来的slider (右边)
为构建这个, 你的皮肤文件必须声明三个皮肤parts: thumb(滑块), track(轨迹条), and dataTip(数据提示)。
MySliderSkin.mxml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
03 | xmlns:s = "library://ns.adobe.com/flex/spark " |
04 | minWidth = "11" minHeight = "100" alpha.disabled = "0.5" > |
06 | [HostComponent("spark.components.VSlider")] |
09 | < s:State name = "normal" /> |
10 | < s:State name = "disabled" /> |
14 | < fx:Component id = "dataTip" > |
15 | < s:DataRenderer minHeight = "24" minWidth = "40" x = "20" > |
16 | < s:Rect top = "0" left = "0" right = "0" bottom = "0" > |
18 | < s:SolidColor color = "0xFFF46B" alpha = ".9" /> |
21 | < s:DropShadowFilter angle = "90" color = "0x999999" distance = "3" /> |
25 | < s:Label id = "labelField" text = "{data}" |
26 | horizontalCenter = "0" verticalCenter = "1" |
27 | left = "5" right = "5" top = "5" bottom = "5" |
28 | textAlign = "center" verticalAlign = "middle" color = "0x555555" /> |
33 | < s:Button id = "track" left = "5" right = "5" top = "0" bottom = "0" skinClass = "MyTrackSkin" /> |
34 | < s:Button id = "thumb" left = "0" right = "0" width = "18" height = "8" skinClass = "MyThumbSkin" /> |
在皮肤中定义了这些皮肤parts这后,组件负责处理他们。它添加事件监听器到滑块,让你可以在轨迹条中拖动滑块。 它还根据相应的value值来定位滑块。请看一下上面示例代码中的MyTrackSkin和MyThumbSkin。你会看到许多FXG的例子。请注意, 自定义的滑块皮肤相比默认Spark滑块皮肤有着完全不同的形状。
"数据提示"皮肤part是动态的 -- 它负责生成和布局。当前的例子中,当你拖动滑块,会在滑块右边弹出数据提示。有了皮肤契约,皮肤可以只管定义皮肤part,和所有可视化方面的内容,而不必担心有什么副作用。 所有的衔接都由组件来处理。
注:一些Flex 4内置组件不仅附加行为到皮肤parts上,同时也会推送数据到皮肤parts。另一种得到皮肤中数据的方法是通过hostComponent属性来拉他们。
当一个组件创建了皮肤时,并非所有的皮肤parts是必需的。 例如,VSlider的数据提示皮肤part并不是必需的。 如果它不存在,就不显示数据提示。
创建可变换皮肤组件
Spark可变换皮肤组件没有在幕后做什么特别的事情。. They have data properties and advertise the skin parts and skin states they need through metadata。 他们还有少许关键的方法来用管理皮肤和皮肤parts的生命周期。您也可以和它一样轻松地创建一个新的换肤组件。
为了演示一下,你可以创建一个简单NoteCard组件,它可以用来在屏幕上显示笔记。在图7所示的例子中,应用程序随机创建多个语录。
图7: NoteCard 组件例子
主应用程序仅创建一个有点旋转的语录NoteCard。 有趣的部分是NoteCard类,它扩展了spark.components.supportClasses.SkinnableComponent类并且在生命周期方法中添加代码。
NoteCard.as:
04 | [SkinState( "normal" )] |
05 | [SkinState( "disabled" )] |
06 | public class NoteCard extends SkinnableComponent |
08 | public function NoteCard() |
13 | [SkinPart(required= "true" )] |
14 | public var labelDisplay:TextBase; |
16 | [SkinPart(required= "false" )] |
17 | public var closeButton:Button; |
19 | private var _text: String ; |
21 | public function get text(): String |
26 | public function set text(value: String ): void |
此组件声明数据属性,皮肤states,皮肤parts。 对于数据,NoteCard有一个公共的text属性。 此外,NoteCard有两个皮肤states,normal和disabled,用SkinStates元数据声明在类声明代码的上面。 这就告诉皮肤,它需要实现这两个states。
NoteCard还有两个皮肤parts,是通过SkinPart元数据声明的。 SkinPart元数就直接声明在皮肤part名称之上。 当前例子,labelDisplay是必需的TextBase类皮肤part,closeButton是一个可选的Button类皮肤part。
由于皮肤是运行时载入,当组件第一次启动时,你不能保证有一定有皮肤。 你也不能保证已经有了全部的皮肤parts,尤其是他们是可选的。 框架负责了这些事情: 衔接part声明和组件属性定义,并且通过皮肤生命周期方法通知组件parts已经准备好了。
实现皮肤states
为实现皮肤states,你需重写getCurrentSkinState()方法以返回皮肤当前所处状态,当前例子中,它会返回"normal" 或"disabled"。当一些事件导致皮肤state变得无效时,组件应该调用invalidateSkinState()方法。 NoteCard.as
03 | [SkinState( "normal" )] |
04 | [SkinState( "disabled" )] |
05 | public class NoteCard extends SkinnableComponent |
08 | override public function set enabled(value: Boolean ) : void |
11 | invalidateSkinState(); |
12 | super .enabled = value; |
15 | override protected function getCurrentSkinState() : String |
当设置enabled属性时,enabled setter调用invalidateSkinState()以通知皮肤,组件的state需要改变,这样getCurrentSkinState()随即会被调用。
处理皮肤parts
处理皮肤parts,有两种主要方法应该要重写,partAdded()和partRemoved()。这些方法会告诉你一个特定的皮肤part被 添加了或被删除了。当装载一个皮肤时Parts将被加入或是删除。皮肤是在运行时交换的,并且延迟加载的,所以只有在某种states情况下或者是一个动 态part刚刚被创建时part才会被加入。在partAdded()方法你可以设置你想要的任何数据到part,而且也可以attach一些事件侦听到 part上。当part被删除时,你应该在partRemoved()方法中做相反的事情。
NoteCard.as
03 | public class NoteCard extends SkinnableComponent |
05 | [SkinPart(required= "true" )] |
06 | public var labelDisplay:TextBase; |
08 | [SkinPart(required= "false" )] |
09 | public var closeButton:Button; |
11 | public function set text(value: String ): void |
18 | labelDisplay.text = value; |
21 | override protected function partAdded(partName: String , instance: Object ) : void |
23 | super .partAdded(partName, instance); |
25 | if (instance == labelDisplay) |
26 | labelDisplay.text = _text; |
27 | if (instance == closeButton) |
28 | closeButton.addEventListener(MouseEvent.CLICK, closeButton_clickHandler); |
31 | override protected function partRemoved(partName: String , instance: Object ) : void |
33 | super .partRemoved(partName, instance); |
35 | if (instance == closeButton) |
36 | closeButton.removeEventListener(MouseEvent.CLICK, closeButton_clickHandler); |
39 | protected function closeButton_clickHandler(event:MouseEvent) : void |
41 | event.stopPropagation(); |
43 | IVisualElementContainer(parent).removeElement( this ); |
在partAdded()方法中,当labelDisplay part加入时,我设置text到这个part。此外,在text属性的setter方法中,我检查,看看是否已经加入labelDisplay,如果是 的话,我重新设置labelDisplay.text为组件的_text值以保证和组件text属性同步。在partAdded()方法中,我添加一个 click事件监听器到closeButton皮肤part。在partRemoved()我一定要删除这个click事件监听器。
作为一个 SkinnableComponent ,你需要做的就是利用这个强大的换肤机制。 当有人创造了某个组件的皮肤,他们必须实现皮肤states和皮肤parts以得到期望的组件行为。 在图6所示的皮肤在样例源代码中可以找到,即使这是一个简单的组件定义,你依然可以用不同的皮肤完全改变它的外观和体验。这就是皮肤真正的力量。
注: 当创建可变换皮肤组件,您可能要决定某些行为是属于皮肤的还是组件。 没有一个明确的硬性的规则。只要能让你的工作更容易就行了。 作为一般指导,一切外观和感观的定义应在皮肤MXML文件中声明。 另一方面,如果有多个皮肤想要某个特殊行为,那么将这个行为放在组件可能是一个好主意。例如,slider中滑块的定位是做在VSlider和 HSlider,没有在皮肤上。
下一步到哪里
Flex 4皮肤发生了重大修改。 明确分开了组件和皮肤。该组件包含了数据,行为和核心逻辑,而皮肤定义了组件的外观和体验。组件由ActionScript编写而皮肤写在MXML中,这 是托FXG和新states语法的福。 组件和皮肤通过皮肤契约进行交互。 又因为它们是各自独立的文件,所以新的皮肤很容易应用到组件上从而完全改变他们的外观。
欲了解更多的Flex 4皮肤信息,请查看 皮肤架构规范 以及 Gumbo组件架构白皮书。