您的位置:首页 > 其它

【转】深入浅出Flex组件生命周期Part2 ─ 使用ActionScript3开发移动Spark组件的皮肤类

2011-09-01 11:29 701 查看

原文地址:http://www.donglongfei.com/2011/07/actionscript-3-customize-mobile-spark-component-part2/

Part2将使用AS3为Part1中已经创建的component类定制Skin类

五. 创建CSS

我们首先创建将要在Skin类中使用的CSS。样式文件为Style.css,建在src的styles目录下。

代码如下:

/* CSS file */
@namespace s "library://ns.adobe.com/flex/spark";
@namespace components "com.mark.demos.components.*";

components|MobileItemRenderer
{
fontSize: 20;
color: #FF0000;
font-family: "_sans";
min-height: 50;
padding-left: 10;
}


为了应用样式,在主应用文件(本例为SparkItemRendererDemo.mxml)中加入样式声明:

<fx:style source="styles/Style.css"/>

六. AS3创建Skin类:MobileItemSkin.as

下面,我们的重头戏开始了:使用AS3为之前的component类MobileItemRenderer创建配套的Skin类。

1. 创建MobileItemSkin.as

在com.mark.demos.skins包下创建ActionScript类MobileItemSkin.as,该类继承spark.skins.mobile.supportClasses.MobileSkin。





使用Flex开发移动应用项目时,Flex4.5提供了一套专为移动设备优化了的类库和组件。这些类和组件为移动设备的优化包括性能和DPI自适应等等,其中MobileSkin发挥了非常重要的作用,这个类是所有适配移动应用的ActionScript Skin类的基类。

我们在Part4会解释如何使用AS3开发适配Web或桌面的Spark组件的Skin类,这时我们会继承spark.skins.SparkSkin。

值得注意的是MobileSkin的父类是UIComponent。(在Part3,我们会深入讨论MobileSkin和SparkSkin。)现在,让我们跳出代码,重新考虑Spark的Skinnable组件这件事情。现在我们已经知道,要创建两个类,一个component类,一个skin类,而两个类都是继承于UIComponent,从这一点上来说两个类没什么本质上的区别,只不过通过Flex框架代码,来让一个类管理数据和逻辑,一个类管理样式和外观。

下面我们来逐步完成Skin类:MobileItemSkin.as,在这个过程中,您不碍多想一想这个类是如何和component类协同的呢?

2. hostComponent

每个Skin类都要声明它的宿主:hostComponent。Skin类的存在目的就是为Component类服务的,失去了Component,Skin类就失去了意义。一列失去灵魂的列车奔驰得再快,也没有任何价值。

《Using Flex 4.5》中说明:

a skin class will define a hostComponent property so that is can get a reference to the component that uses it. This is not required by the skinning contract but is recommended. Skin classes can also share data with their host component by using data binding.

hostComponent帮助Skin从Component中“拉”数据,通过这种方式获取开发者设定在component类上的属性以及相关变化。

下面代码为我们的MobileItemSkin类,设定了hostComponent。需要注意的是hostComponent的类型:MobileItemRenderer。如我们之前说到的,Skin类为Component类提供的服务是“专项”服务,因此,这里hostComponent类型必须与本Skin类的服务目标component类类型一致。

private var _hostComponent:MobileItemRenderer;
public function get hostComponent():MobileItemRenderer
{
return _hostComponent;
}

public function set hostComponent(value:MobileItemRenderer):void
{
_hostComponent = value;
}


3. createChildren()

接下来,我们要为component类来“组装”Skin。在本例中,就像MobileItemRenderer中通过SkinPart元数据标签已经说明的,我们要为其装配一个StyleableTextField或其子类类型的文本。我们并不是一步到位的为Skin生成该对象,并安装到位。还记得吗?Flex架构非常重要的工作就是“延迟计算”,因此,首先,我们要做的只是“创建子对象”: createChildren。

我们通过覆盖createChildren方法来完成这项任务。(很多时候,编写自定义组件感觉就像完成一道填空题,尤其对那些刚刚掌握创建自定义组件的开发者来说,我们尽量在Part3来为您指明这些“填空”题的出题者的思路和想法。)

我们来完成第一项填空题:覆盖createChildren方法。在此之前,你需要声明一个StyleableTextField类型的变量labelDisplay。这帮助你完成了Component和Skin类之间的一项“契约”。

public class MobileItemSkin extends MobileSkin
{

public var labelDisplay:StyleableTextField;


下面是createChildren方法真正完成创建labelDisplay的方法,在该方法中使用addChild把labelDisplay添加在显示列表中。

override protected function createChildren():void{
addLabel();
}

private function addLabel():void{
labelDisplay = new StyleableTextField();
addChild(labelDisplay);
}


4. commitProperties()

如果你了解UIComponent的生命周期,你应该知道在UIComponent的生命周期中,Flex会依序调用createChildren,commitProperties,measure,layoutChrom和updateDisplayList方法,这些方法依次完成了各自专有的任务,其中方法commitProperties,measure和updateDisplayList会在UIComponet创建后,调用invalidateProperties,invalidateSize和invalidateDisplayList方法后,在下一帧时再次被调用,

鉴于MobileSkin父类依然是UIComponent,因此这些基本原理依然适用。

(Part3问题:如果你是一个究甚解的开发者,你会注意到在Skinnable Spark Component这个体系里存在着Component类和Skin类两个UIComponent子类,也就意味着,在Skinnable Component的生命周期中,存在着之前我们说到的两套UIComponent生命周期。在深入理解Spark Component生命周期的过程中,这个事实经常会混淆开发者。)

在本例中,我们使用Skin类的commitProperties来为labelDispaly定义外观属性。代码如下:

override protected function commitProperties():void{
super.commitProperties();
styleLabel();
}

private function styleLabel():void{
if(labelDisplay && hostComponent && hostComponent.data){
if(currentState=="selected"){
labelDisplay.text= "远离高铁";
}
if(currentState=="normal"){
labelDisplay.text= hostComponent.data.label;
}
labelDisplay.defaultTextFormat = new TextFormat( getStyle( "fontFamily" ), getStyle( "fontSize" ),getStyle("color") );
labelDisplay.commitStyles();
}
}


需要说明的是styleLabel方法。

在该方法中,我们通过判断当前的状态(依据currentState变量)来为labelDisplay设定外观。(Part3问题:这个神秘的currentState从哪里来?又由谁赋值? )

在开发自定义组件时,要意识到,这段代码会在组件未出生开始就会执行,因此,应该时时判断要处理的部件或者其他类似hostComponent之类的对象是否已经实例化,不能想当然地使用。

Part3问题:在component类中,我们也声明了labelDisplay,那么此labelDisplay和彼labelDisplay有何关系?在《Using Flex4.5》或者很多讲述Spark架构的文章都说,我们很少会为component类实现commitProperties方法(彼方法不同于此方法),那么,这是为什么呢?

5. measure()

我们已经设定了labelDisplay的外观,接下来,我们需要告知Flex本组件的默认最小尺寸和默认尺寸:覆盖measure方法。

override protected function measure():void{
super.measure();
var h:Number=0;
var w:Number=0;

if(labelDisplay){
w=labelDisplay.textWidth + getStyle("paddingLeft");
h=labelDisplay.textHeight;
}

measuredMinHeight = getStyle( "minHeight" );
measuredHeight = Math.max(h,measuredMinHeight);
measuredWidth=w;
measuredMinWidth=Math.max(unscaledWidth,w);
}


在上述代码中,我们通过getStyle获取CSS样式单中指定的minHeight来确定最小值。如果你深入阅读LabelItemRenderer.as和IconRenderer.as源码,你会发现measure方法要考虑的问题很多,本例中做了简化。

6. layoutContents()方法

按照UIComponet的“填空题”应试法则,下面我们应该完成的是UpdateDisplayList方法,来实现最终的布局。但是MobileSkin覆盖了UIComponent类的UpdateDisplayList方法,新的UpdateDisplayList方法摘录于下:

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
graphics.clear();

super.updateDisplayList(unscaledWidth, unscaledHeight);

layoutContents(unscaledWidth, unscaledHeight);

if (useSymbolColor)
applySymbolColor();

drawBackground(unscaledWidth, unscaledHeight);
}


可以看到,updateDisplayList的主要工作被分为layoutContents和drawBackground两个方法。因此,对于创建移动应用组件皮肤,我们很少会直接覆盖updateDisplayList方法,而是覆盖layoutContents和drawBackground方法。

本例中,我们覆盖layoutContents方法来布置我们的部件labelDisplay。

override protected function layoutContents(unscaledWidth:Number, unscaledHeight:Number):void{
var labelWidth:Number=0;
var labelHeight:Number=0;

if(labelDisplay){
labelDisplay.width=unscaledWidth;
labelDisplay.x = getStyle( "paddingLeft" );
labelDisplay.y = (unscaledHeight - labelDisplay.textHeight ) / 2;
}

// we draw a separator line between each item
var lineY:int = unscaledHeight -1;
graphics.clear();
graphics.lineStyle( 1 );
graphics.moveTo( 0, lineY );
graphics.lineTo( unscaledWidth, lineY );
}


我们设置了labelDisplay的坐标,并且在其下边缘绘制了一条1像素的横线。

7. 更新状态

实际上,现在你就可以使用MobileItemRenderer了,但是如果你把他作为itemRenderer赋给List后,运行后,你会发现 MobileItemRenderer并没有及时的响应用户操作,更新状态。

我们还需要覆盖神秘的commitCurrentState()方法,来为Skin类更新状态。

override protected function commitCurrentState():void{
super.commitCurrentState();
styleLabel();
}

如果你看过Part1,你会记得,我们已经为Component类MobileItemRenderer.as声明了SkinState:normal和selected。在用户选中本item后,List会调用selected的set方法,然后该方法又会调用invalidateItemState来通知Flex更新状态。我们也按照要求,让Component类MobileItemRenderer乖乖的提供了方法getCurrentSkinState,来通知某人我目前的状态。而在我们的Skin类中,很明显,某个神秘先生又通过currentState变量告知了当前状态,而前提是,我们要乖乖的实现commitCurrentState方法。

各位看管,明白了吗?

Flex在布一个很大的局!有一双“手”在操控着“不明真相的群众”。群众们只要按要求做好填空题,自然有全国最牛拜的机构-“相关机构”,帮你把文章写完!

然而,作为一个有智商的现代码农,我们要求信息透明,因此,不才在Part3中将为你解释这个神秘的相关机构。

我们的commitCurrentState方法如下:

override protected function commitCurrentState():void{
super.commitCurrentState();
styleLabel();
}


七. 调用MobileItemRenderer,完成View

目前,我们已经完成了Component类和Skin类,比较简陋,但是在Part3您会看到他的价值。

完成示例项目中的SparkItemRendererDemoView.mxml,测试这个MobileItemRenderer。

< ?xml version="1.0" encoding="utf-8"?>
<s:view xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark" title="主页视图" xmlns:components="com.mark.demos.components.*">

<fx:declarations>
<!-- 将非可视元素(例如服务、值对象)放在此处 -->
</fx:declarations>
<s:list width="100%" height="100%" labelField="label">
<s:dataprovider>
<s:arraycollection>
<fx:object label="珍惜生命"/>
<fx:object label="珍惜生命"/>
<fx:object label="珍惜生命"/>
<fx:object label="珍惜生命"/>
<fx:object label="珍惜生命"/>
<fx:object label="珍惜生命"/>
</s:arraycollection>
</s:dataprovider>

<s:itemrenderer>
<fx:component>
<components:mobileitemrenderer />
</fx:component>
</s:itemrenderer>

</s:list>
</s:view>


运行后,您会得到如下结果:





八. 未完待续…

上帝花七天创造了世界,问题是,他是怎么做的呢?

如果你依照步骤看完了Part1和Part2,那么,这也许是最“疑惑重重”的教程之一,我们为您演示了如何创建一个Spark架构下Skinnable组件类。但是,又带了了如此多的问题:

为什么Skin一定要提供与Component类中SkinPart指定名称一致的部件变量?Component类中的SkinPart和Skin类中的对象有何关系?
component类如何知道skin类添加了SkinPart,继而来调用partAdded呢?
partAdded中,我们直接使用了labelDisplay来添加侦听器?可labelDispaly从何而来?与instance是和关系
invalidateSkinState是如何通知Skin类的呢?
Skin类是如何调用getCurrentSkinState的呢?
component类和skin类之间的“契约”源于何处?
Skin类和Component类如何协同,两套UIComponent生命周期又如何交错在一起
currentState从何而来?
component类的commitProperties方法和Skin类的commitProperties方法?

朋友,让我们在Part3中再见,揭开这些为什么。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: