[转]深入浅出Flex组件生命周期Part1─ 使用ActionScript3开发Spark组件Component类

前言

Flex开发移动应用时,出于性能考虑,需要使用AS3编写组件Skin,而不是使用MXML。实际上,通过使用AS3编写组件皮肤,开发者可以更深入的了解Flex的组件生命周期,无论是对于移动应用开发还是传统的桌面或者Web应用开发,都大有裨益。
本文通过一个实例,来展示如何使用AS3开发一个Spark组件和对应的移动组件Skin,更重要的是解释与之相关的Spark组件生命周期知识。

关于本系列文章

Spark架构的目的就是分离组件行为和可视化。在Spark框架中,组件的数据和逻辑行为由组件类负责,而可视化剥离到Skin中。组件和其Skin间维护一个所谓的“契约”。本文不具体解释Spark架构的基本概念,如果您不了解Spark的基础知识,可以参考Deepa姐姐的文章 “ Spark架构和组件集的简要概述”。

本文将为你揭示Spark架构背后的秘密。本文介绍的内容和例子是基于Flex4.5的移动架构上,但是其中关于Spark架构的介绍99%通用于非移动框架的Spark组件。为了更好的理解本文,建议您下载Flex4.5 SDK源码。

此外,亲,我们在本文中不会针对Spark架构容器展开讨论,好吗?    …… 好滴。

我们的终极目标

Flex移动应用中,最频繁使用的组件之一可能就是spark.components.LabelItemRenderer和spark.components.IconItemRenderer了。IconItemRenderer继承自LabelItemRenderer,而LabelItemRenderer扩展了UIComponent。
这里有一点问题。如果参看Adobe官方文档《
Using Adobe Flex4.5》, 在“Create advanced Spark visual components in
ActionScript”一章有如下描述:

Create a skinnable component by creating a subclass of the SkinnableComponent class.
A skinnable component uses two files: one for the component definition and one for the skin definition. Create a Spark skinnable component as a subclass of the SkinnableComponent class.

Spark体系的Skinnable组件应该是继承自SkinnableComponent。每一个组件包含两个文件,一个是组件类(继承自SkinnableComponent),一个是皮肤类。很显然,IconItemRenderer和LabelItemRenderer都不属于SkinnableComponent,其弊端在于每次如果你需要深度定制ItemRenderer,都不得不扩展这两个类,覆盖其中相关方法。

Part1的目标是使用ActionScript创建一个真正Skinnable的ItemRenderer。为了简单起见,我们以LabelItermRenderer为例。

值此铁道部为难之际,草民望借此本系列文章,帮助屁民程序员们,更加深入的理解Spark架构,为创建一个和谐的社会而努力。

一. 开发Spark组件和mobile应用适用的Skin类的步骤

B车追上A车,需要一个步骤:电A车!

而创建Spark架构下Skinnable的组件需要两个主要步骤:

  1. 创建负责数据和非可视化逻辑的组件类:该类将继承SkinnableComponent 。本例中需要额外两个接口mx.core.IDataRenderer和spark.components.IItemRenderer,以便最终用于List的ItemRenderer。
  1. 创建负责可视化的Skin类:由于我们的目标是为移动应用创建组件Skin,因此该类继承spark.skins.mobile.supportClasses.MobileSkin。

最复杂的是把大象关进冰箱,具体方法超出本文讨论范畴。

二. 创建Flex手机项目

启动FlashBuilder4.5,点击“文件->新建”创建“Flex手机项目”,如下图。

跟随向导选择手机操作系统和应用模板。本例中选择的模板是”基于视图的应用程序”。FlashBuilder将为我们创建空的手机应用项目,在本例中,我们最终所创建项目的项目结构如下:

三. 创建Component类

1. 创建继承SkinnableComponent,实现IDataRenderer和IItemRenderer的AS3类:MobileItemRenderer.as

在src目录下,创建路径为com.mark.demos.components的package。在该package下,通过菜单“新建->ActionScript Skinnable组件”创建Skinnable组件类。

在创建向导中(如下图),指定该类名为MobileItemRenderer。可以看到该类的超类默认设置为spark.components.supportClasses.SkinnableComponent。SkinnableComponent是所有符合Spark架构的Skinnable组件类的基类,其父类是UIComponent。我们在Part3中会深入讨论这个类。

同时,由于我们要创建可以被List使用的itemRenderer,因此需要实现IDataRenderer和IItermRenderer两个接口。把两个接口添加到接口列表中。

点击“完成”后,我们就得到了FlashBuilder自动创建的“半成品”。在MobileItemRenderer的半成品中,FlashBuilder已经创建了实现接口所需要的Set和Get空方法。

2. 完成需要的IDataRenderer和IItemRenderer接口方法

处于简化目的,本例中并不完成所有的接口方法。我们主要实现data、itemIndex和selected的set和get方法。实现IDataRenderer接口的data的set和get方法之目的在于使我们自定义的MobileIterRenderer能够从List中获取到数据。实现IItemRenderer接口的itemIndex和selected的set和get方法的目的是,能够从List获取到条目的用户选择状态和index。关于两个接口的具体说明请参见AS3参考中的IItemRendererIDataRenderer说明。

首先创建三个变量:

		private var _itemIndex:int;
		private var _data:Object;
		private var _selected:Boolean=false; 

接下来,分别完成data的set和get方法,以及itemIndex的get方法。 itemIndex的set方法比较复杂,本例暂不实现。如果你希望进一步了解,可以参考spark.components.LabelItemRenderer类源码中的itemIndex set方法。

		[Bindable("dataChange")]
		public function get data():Object
		{
			return _data;
		}
 
		/**
		 *  @private
		 */
		public function set data(value:Object):void
		{
			_data = value;
			if (hasEventListener(FlexEvent.DATA_CHANGE))
				dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
		}
 
		public function get itemIndex():int
		{
			return _itemIndex;
		}
 
		public function set itemIndex(value:int):void
		{
			//此方法本例中暂不实现。
		}
 
		public function get selected():Boolean
		{
			return _selected;
		}
 
		public function set selected(value:Boolean):void
		{
			if( _selected != value)
			{
				_selected = value;
			}
		}

    我们之后还会修改selected的set方法,因为该属性的变化会触发我们自定义的component类MobileItemRenderer的状态变化。

3. 声明SkinPart

SkinPart实际上最终就是Skin类的一个子组件,由Skin类将其加入到显示列表,或者从其中删除。而component类则声明本component需要哪些SkinPart,对应的Skin类要严格照办。
我们的MobileItemRenderer类极其简陋,只需要一个用来展示label信息的SkinPart。因此,我们声明StyleableTextField类型的SkinPart如下,其名称为labelDisplay。设置required=true要求MobileItemRenderer的配套Skin类必须提供名为labelDisplay的部件(即变量),该变量必须为StyleableTextField或者其子类类型。(Part3问题一:为嘛Skin一定要提供与SkinPart指定名称一致的部件变量?为嘛类型必须一致?)

	public class MobileItemRenderer extends SkinnableComponent implements IDataRenderer, IItemRenderer
	{
		[SkinPart(required="true")]
		public var labelDisplay:StyleableTextField;
 
		......

4. 覆盖PartAdded和partRemoved方法

当Skin类将对应的SkinPart加入到显示列表中时(通过addChild),component类会调用partAdded方法。当移除时,会调用partRemoved方法。
partAdded和partRemoved使我们可以在SkinPart加入显示列表或者从显示列表中移除时获得通知,并在component类中做相应的操作。比如,我们可以覆盖这两个方法为SkinPart添加或删除事件侦听器等。(Part3问题二:component类如何知道skin类添加了SkinPart,继而来调用partAdded呢?)
本例中,我们覆盖这两个方法来加入double click的事件侦听器,当用户双击component类的labelDisplay时,修改显示文本。如下:

		override protected function partAdded(partName:String, instance:Object) : void
		{
			super.partAdded(partName, instance);
			if(partName=="labelDisplay"){
				labelDisplay.addEventListener(MouseEvent.DOUBLE_CLICK,changeLabel);
			}
		}
 
		override protected function partRemoved(partName:String, instance:Object) : void
		{
			super.partRemoved(partName, instance);
			if(instance==labelDisplay){
				labelDisplay.removeEventListener(MouseEvent.DOUBLE_CLICK,changeLabel);
			}
		}
 
		private function changeLabel(event:MouseEvent):void{
			var obj:StyleableTextField=event.target as StyleableTextField;
			obj.text="小心雷电";
		}

partAdded方法的参数partName说明了加入到显示列表中的部件名称,该名称对应之前SkinPart声明的变量名(如果有多个SkinPart,则应对应其中某个)。instance为加入到显示列表中的SkinPart对象,在本例中即为Skin类通过addChild加入进来的StyleableTextField类型的labelDisplay对象。

我们可以如partAdded方法中一样通过partName来判断,也可以如partRemoved方法中使用instance来判断加入的对象是哪个SkinPart。
在这个例子中我们使用partAdded和partRemoved修改了label的显示。实际上,同样的工作也可以放在Skin中进行。从技术上讲,并没有一个严格的界限说明类似的操作必须在component类中执行或者必须在对应的Skin类中执行,这个界限是模糊的。
(Part3问题三:为什么我们通过这两种方式都可以判断加入的对象是哪个SkinPart?为什么我们可以使用component类< 本例中即MobileItemRenderer>的labelDisplay来注册侦听器?我们并没有实例化该labelDisplay,因此上述代码应该抛出null异常吗?难道我们不是应该使用传递进来的instance来注册事件侦听器吗?)

5. 声明SkinState

接下来,为我们的组件声明状态。MobielItemRenderer只有两个状态normal和selected。状态声明要在类外。

	[SkinState("normal")]
	[SkinState("selected")]
	public class MobileItemRenderer extends SkinnableComponent implements IDataRenderer, IItemRenderer
	{

默认情况下,组件的状态为normal,当List通知item被选中后(通过selected的set方法设置selected为true时),则组件状态变为selected。当selected变为false时,则状态切换回normal。在Flex应用中,组件数据模型的变化会引发组件状态的变化,而状态的变化则会触发可视界面的变化。因此,component类本身并不负责处理状态,component类仅仅声明了Skin类的状态,并根据数据模型的变化来反馈当前状态,而Skin类要负责针对不同状态实现可视化效果。
由此可见,我们需要修改selected的set方法,通知Skin类,状态发生了变化。我们通过调用invalidateSkinState方法,来通知对应的Skin类状态发生了变化,而Skin类会在下一帧时来渲染新状态下的界面。
Skin类是通过Component类的getCurrentSkinState方法获取新状态的。

(part3问题四:invalidateSkinState是如何通知Skin类的呢?)

6. 覆盖getCurrentSkinState方法

component类通过getCurrentSkinState方法来通知Skin类当前状态。在该方法中,我们通过component类的数据模型来判断当前处于哪种状态。

		override protected function getCurrentSkinState():String{
			var returnState:String="normal1";
			if(_selected){
				returnState="selected";
			}
			return returnState;
		}

(part3问题五:Skin类是如何调用getCurrentSkinState的呢?)

目前为止,我们已经创建了一个非常基本的component类,实际上我们没有为该类实现一点点关于可视化的部分,这写任务都将由Skin类来完成。而Spark组件生命周期中最神秘也最有趣的地方就是,component类是如何同与其对应的skin类相互配合互动的。
在本系列的Part2,我们将为MobileItemRenderer创建Skin类,并提出更多问题。


原文地址:http://www.donglongfei.com/2011/07/actionscript-3-customize-spark-component-part1/?replytocom=176

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值