Flex4 Skinning 4: 留言板示例
上一篇随笔讲了skinnable Spark component和skin之间的交互,其实在这些components背后并没有太高深的理论,它们只是通过元数据的方式定义一些skin part和skin state, 并在相关的属性上进行关联。自然有的时候我们需要override一些管理skin part和skin state生命周期的关键方法。本篇随笔试着创建一个skinnable Spart component.
来看一个简单的类似留言板的示例。首先看一下想要做成的效果,如图5所示。
Figure 5. 可以在一个面板上粘贴留言条
仔细观察可以发现每一个留言条都是有一些简单的控件组合而成,单独存在的留言条如图6所示。
Figure 6. 一个单独的留言条由文本标签和圆形关闭按钮组合而成
下面我们想新建这个组件,新建一个AS3类,名为NoteCard, 并继承skinnableComponent父类。如下代码所示:
package com.guyue.components { import spark.components.supportClasses.SkinnableComponent; public class NoteCard extends SkinnableComponent { public function NoteCard() { super(); } } }
然后为这个类用SkinState元数据方式添加相应的skin state, 一般情况下有两个:normal和disabled. 代码如下:
[SkinState("normal")] [SkinState("disabled")]
接下来用SkinPart元数据方式定义需要的skin part, 如前面分析,一个简单的留言条由文本标签和圆形按钮组成,因此这里对其进行分别定义为labelDisplay和closeButton, 类型分别为TextBase和Button. 并注意closeButton的required=false, 代表了labelDisplay必须在skin文件中有对应id的标签,而closeButton则是可选的。元数据声明位于skin part名称也就是NoteCard类属性的上方。代码如下:
[SkinPart(required="true")] public var labelDisplay:TextBase; [SkinPart(required="true")] public var closeButton:Button;
接下来需要为skin part定义相关的属性。比如labelDisplay的text属性。代码如下:
private var _text:String; public function get text():String { return _text; } public function set text(value:String):void { if(_text == value) return; _text = value; if(labelDisplay) labelDisplay.text = value; }
由于skin可以在运行时加载或者卸载,因此我们在程序刚开始运行时并不能保证component的相关skin文件已经准备完毕。所以需要对相应skin part属性进行动态设置。Flex框架会将skin中声明的skin part与component中的相关属性结合起来,并通过skinning生命周期中相应的方法进行通知。
这里的NoteCard类用到的相关方法主要有getCurrentSkinState(), partAdded(), partRemoved()这三个。这三个方法都在SkinnableComponent中有定义,因此只需要在子类中进行覆写。
getCurrentSkinState()方法是返回要要加载skin的component的state名称。比如这里的NoteCard类有disabled, enabled, 实现代码如下:
override protected function getCurrentSkinState():String { if(!enabled) return "disabled"; return "normal"; }
partAdded()方法会在skin part添加的时候被调用,因此在这个方法中,我们可以添加这个skin part的相关处理逻辑,比如事件监听等。这里的NoteCard类实现代码如下:
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); }
partRemoved()方法会在skin part移除的时候被调用,因此在这个方法中,我们可以将添加在这个skin part的相关处理逻辑进行移除,比如事件监听等。这里的NoteCard类实现代码如下:
override protected function partRemoved(partName:String, instance:Object):void { super.partRemoved(partName, instance); if(instance == closeButton) closeButton.removeEventListener(MouseEvent.CLICK, closeButton_clickHandler); }
可以看出partAdded()方法与partRemoved()方法是相对应的,其中用到的closeButton_clickHandler事件主要是将先前添加的留言条进行移除,实现方法如下:
protected function closeButton_clickHandler(event:MouseEvent):void { event.stopPropagation(); IVisualElementContainer(parent).removeElement(this); }
以上只是建立了SkinnableComponent, 接下来需要为它定义相关的皮肤才能达到想要的效果。在skins包中新建一个MXML Skin文件NoteCardSkin, 定义其HostComponent元数据属性为刚刚建立的NoteCard类。代码如下:
<?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" xmlns:mx="library://ns.adobe.com/flex/mx"> <!-- host component --> <fx:Metadata> [HostComponent("com.guyue.components.NoteCard")] </fx:Metadata> </s:Skin>
接下来定义与NoteCard类中对应的states, 代码如下:
<s:states> <s:State name="normal"/> <s:State name="disabled"/> </s:states>
接下来是界面的组成部分,包括一个矩形,一个矩形中的文本标签和一个圆形按钮。最后NoteCardSkin皮肤的代码如下:
<?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" xmlns:mx="library://ns.adobe.com/flex/mx"> <!-- host component --> <fx:Metadata> [HostComponent("com.guyue.components.NoteCard")] </fx:Metadata> <!-- states --> <s:states> <s:State name="normal"/> <s:State name="disabled"/> </s:states> <!-- --> <s:Rect width="300" height="200"> <s:stroke> <s:SolidColorStroke color="0xAA8E5E" weight="4"/> </s:stroke> <s:fill> <s:LinearGradient> <s:GradientEntry color="0x60c2fe"/> <s:GradientEntry color="0x70b2ee"/> </s:LinearGradient> </s:fill> </s:Rect> <s:Label id="labelDisplay" left="10" top="10" width="280" height="180" color="0xFFF3E9" lineBreak="toFit" fontSize="18" fontFamily="Chalkboard"/> <s:Button id="closeButton" skinClass="com.guyue.skins.NoteCardCloseButtonSkin" top="5" right="5"/> </s:Skin>
注意观察一下Label和Button的id属性,可以发现与NoteCard类中的SkinPart是对应的,这样在将skin应用到component上时,Flex会自动匹配。
另外圆形按钮closeButton也使用了一个自定义皮肤,使用这个皮肤会将Flex4自带的默认皮肤覆盖,这里我们需要再建立这个新的皮肤文件NoteCardCloseButtonSkin, 具体方法与上大同小异,代码如下:
<?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" xmlns:mx="library://ns.adobe.com/flex/mx"> <!-- host component --> <fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata> <!-- states --> <s:states> <s:State name="disabled" /> <s:State name="down" /> <s:State name="over" /> <s:State name="up" /> </s:states> <s:Ellipse width="16" height="16"> <s:stroke> <s:SolidColorStroke color="0x131313" weight="1" /> </s:stroke> <s:fill> <s:LinearGradient> <s:GradientEntry color="0xAA8E5E" color.over="0x9A7E4E" color.down="0x7A5E2E" /> <s:GradientEntry color="0xCCBBAA" color.over="0xBCAB9A" color.down="0x9C8B7A"/> </s:LinearGradient> </s:fill> </s:Ellipse> <s:Label text="X" horizontalCenter="0" verticalCenter="0" /> </s:Skin>
以上就是一个简单skinnable Spark component实现的示例。Flex强大的skinning机制使得它的实现非常容易。我们可以随时定义新的皮肤来完全改变这个component的外观。
人每天要做三件事,第一件是微笑,第二件是欢笑,第三件是大笑。