英文原文: http://www.adobe.com/devnet/flex/articles/flex4_skinning.html
本文翻译原创链接: http://www.smithfox.com/?e=34 转载请注明
翻译: smithfox
上接:
http://smithfox.iteye.com/admin/blogs/847465
皮肤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" ?> |
02 |
< s:Skin xmlns:fx = "http://ns.adobe.com/mxml/2009 " |
03 |
xmlns:s = "library://ns.adobe.com/flex/spark " |
04 |
minWidth = "11" minHeight = "100" alpha.disabled = "0.5" > |
05 |
< fx:Metadata > |
06 |
[HostComponent("spark.components.VSlider")] |
07 |
</ fx:Metadata > |
08 |
< s:states > |
09 |
< s:State name = "normal" /> |
10 |
< s:State name = "disabled" /> |
11 |
</ s:states > |
12 |
|
13 |
< fx:Declarations > |
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" > |
17 |
< s:fill > |
18 |
< s:SolidColor color = "0xFFF46B" alpha = ".9" /> |
19 |
</ s:fill > |
20 |
< s:filters > |
21 |
< s:DropShadowFilter angle = "90" color = "0x999999" distance = "3" /> |
22 |
</ s:filters > |
23 |
</ s:Rect > |
24 |
|
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" /> |
29 |
</ s:DataRenderer > |
30 |
</ fx:Component > |
31 |
</ fx:Declarations > |
32 |
|
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" /> |
35 |
|
36 |
</ s:Skin > |
在皮肤中定义了这些皮肤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:
01 |
package |
02 |
{ |
03 |
|
04 |
[SkinState( "normal" )] |
05 |
[SkinState( "disabled" )] |
06 |
public class NoteCard extends SkinnableComponent |
07 |
{ |
08 |
public function NoteCard() |
09 |
{ |
10 |
super (); |
11 |
} |
12 |
|
13 |
[SkinPart(required= "true" )] |
14 |
public var labelDisplay:TextBase; |
15 |
|
16 |
[SkinPart(required= "false" )] |
17 |
public var closeButton:Button; |
18 |
|
19 |
private var _text: String ; |
20 |
|
21 |
public function get text(): String |
22 |
{ |
23 |
return _text; |
24 |
} |
25 |
|
26 |
public function set text(value: String ): void |
27 |
{ |
28 |
if (_text == value) |
29 |
return ; |
30 |
_text = value; |
31 |
} |
32 |
|
33 |
... |
34 |
} |
35 |
} |
此组件声明数据属性,皮肤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,你需重写getCurrentSkinState()方法以返回皮肤当前所处状态,当前例子中,它会返回"normal" 或"disabled"。当一些事件导致皮肤state变得无效时,组件应该调用invalidateSkinState()方法。 NoteCard.as
01 |
package |
02 |
{ |
03 |
[SkinState( "normal" )] |
04 |
[SkinState( "disabled" )] |
05 |
public class NoteCard extends SkinnableComponent |
06 |
{ |
07 |
... |
08 |
override public function set enabled(value: Boolean ) : void |
09 |
{ |
10 |
if (enabled != value) |
11 |
invalidateSkinState(); |
12 |
super .enabled = value; |
13 |
} |
14 |
|
15 |
override protected function getCurrentSkinState() : String |
16 |
{ |
17 |
if (!enabled) |
18 |
return "disabled" ; |
19 |
return "normal" |
20 |
} |
21 |
|
22 |
... |
23 |
} |
24 |
} |
当设置enabled属性时,enabled setter调用invalidateSkinState()以通知皮肤,组件的state需要改变,这样getCurrentSkinState()随即会被调用。
处理皮肤parts,有两种主要方法应该要重写,partAdded()和partRemoved()。这些方法会告诉你一个特定的皮肤part被 添加了或被删除了。当装载一个皮肤时Parts将被加入或是删除。皮肤是在运行时交换的,并且延迟加载的,所以只有在某种states情况下或者是一个动 态part刚刚被创建时part才会被加入。在partAdded()方法你可以设置你想要的任何数据到part,而且也可以attach一些事件侦听到 part上。当part被删除时,你应该在partRemoved()方法中做相反的事情。
NoteCard.as
01 |
package |
02 |
{ |
03 |
public class NoteCard extends SkinnableComponent |
04 |
{ |
05 |
[SkinPart(required= "true" )] |
06 |
public var labelDisplay:TextBase; |
07 |
|
08 |
[SkinPart(required= "false" )] |
09 |
public var closeButton:Button; |
10 |
|
11 |
public function set text(value: String ): void |
12 |
{ |
13 |
if (_text == value) |
14 |
return ; |
15 |
_text = value; |
16 |
|
17 |
if (labelDisplay) |
18 |
labelDisplay.text = value; |
19 |
} |
20 |
|
21 |
override protected function partAdded(partName: String , instance: Object ) : void |
22 |
{ |
23 |
super .partAdded(partName, instance); |
24 |
|
25 |
if (instance == labelDisplay) |
26 |
labelDisplay.text = _text; |
27 |
if (instance == closeButton) |
28 |
closeButton.addEventListener(MouseEvent.CLICK, closeButton_clickHandler); |
29 |
} |
30 |
|
31 |
override protected function partRemoved(partName: String , instance: Object ) : void |
32 |
{ |
33 |
super .partRemoved(partName, instance); |
34 |
|
35 |
if (instance == closeButton) |
36 |
closeButton.removeEventListener(MouseEvent.CLICK, closeButton_clickHandler); |
37 |
} |
38 |
|
39 |
protected function closeButton_clickHandler(event:MouseEvent) : void |
40 |
{ |
41 |
event.stopPropagation(); |
42 |
|
43 |
IVisualElementContainer(parent).removeElement( this ); |
44 |
} |
45 |
} |
46 |
} |
在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组件架构白皮书。