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的外观。

posted @ 2010-11-08 12:22  spoony  阅读(1705)  评论(3编辑  收藏  举报