翻译:将您的Flex组件从MXML迁移至ActionScript 3

这是来自RIAMeeting翻译小组的一篇译文,推荐给大家,原文地址是:

http://insideria.com/2010/05/moving-your-flex-components-fr.html

我经常听说,比较酷的开发者都用 ActionScript写他们的Flex组件而不用MXML。 我不太认同这种观点。 对布局而言,MXML是强大的。 对于构建简单的组件来说也是强大的。声明语法让许多开发工作更容易些,例如设置样式和添加事件监听。 但是,如果您仔细研读Flex框架源代码或者商业级别的组件,如我在Flextras构建的那些,您会注意到他们并没有使用MXML。 用ActionScript(AS)就完全可以构建。 那是为什么呢? 

ActionScript能让你粒度级地控制你的代码(非常灵活)。MXML是ActionScript语言的另外一种表述形式。Flex框架分析MXML并将其转换成ActionScript。这意味着您写的代码并不是可以立即运行的代码。 若将编码看做是做饭的话,使用MXML就如同从商店里购买现成的蛋糕材料,您只需要加点水就可以烤蛋糕了。 而ActionScript类似于先从面粉开始,并仔细挑选好其他调料才能烤蛋糕。它费时较长,需要考虑的东西也多一些,但往往比较值得。 

这篇文章会向您展示怎样将您的组件开发从MXML迁移到ActionScript。顺便我们会接触到Flex组件生命周期的各个方面。 一般来说,当您在受约束的环境下建自己的应用程序时,用MXML会更有意义;而且确实也不错。 但是,懂得怎样用ActionScript从头创建,会使您对Flex的工作机制有更深入的了解。 

今天的应用程序

今天,我希望您假设,您的老板让您做一个问卷调查程序。 就如同大部分调查一样,这个程序包括一大堆的问题,需要一些收集答案的方式。 大部分问题都可以用是或否来回答。在正常情况下,您会用单选按钮来获取是或否的回答。 

不幸的是,您需要跟我试着想想您的老板有一点儿变态。 他讨厌单选按钮。 您永远也不会知道为什么,但还必须这么做。 他坚持让您用下拉框来代替单选按钮。 好吧!我们可以做到。 

既然知道这个调查程序与很多“是”或“否”的回答相关联,您决定据此做个组件。 

YesNoQuestion组件版本1

那让我们来投入创建组件的第一个版本。 您可以用Text组件来显示是或否,用ComboBox来做选择。 然后把这些全放进HBox里,代码看起来像这样:

1.         <?xml version="1.0" encoding="utf-8"?>
2.         <mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%">
3.               <mx:Script>
4.                     <![CDATA[
5.                           import mx.collections.ArrayCollection;
6.                           [Bindable] 
7.                           public var dp : ArrayCollection = new ArrayCollection([
8.         {label:'Yes'},
9.         {label:'No'}
10.                      ]);
11.                ]]>
12.          </mx:Script>
13.    <mx:Text id="question" />
14.     
15.    <mx:ComboBox id="answer" dataProvider="{dp}" />
16.     
17.    </mx:HBox>

我们已经用别的MXML组件做了第一个ActionScript的应用。 ComboBox的dataProvider是脚本编写的, 它包含了两个对象,一个是,一个否。 

不幸地是,这个组件依然缺少功能。 当用这个组件的时候,我们怎么指定问题文本呢? 您可以用“question.text”,但是如果我们能让他简单点儿会更好。我们怎么知道被调查者选了什么答案呢? 我们也需要增加属性来描述那个值。 

在ActionScript代码块添加这两个变量:

1.         [Bindable] 
2.         public var questionText : String;
3.          
4.         [Bindable] 
5.         public var selectedAnswer : String;

既然变量是公共的,人们就很容易用我们的组件获取这些变量。 我称之为变量属性,尽管我认为这并非正式名称。 接下来,您将会将变量绑定到这两个组件。 像这样修改MXML:

1.         <mx:Text id="question" text="{questionText}" />
2.          
3.         <mx:ComboBox id="answer" dataProvider="{dp}" change="selectedAnswer = answer.selectedItem.label" />

数据绑定将问题文本绑定至文本显示。 ComboBox值改变时,您可以用change事件来更新已选择的答案。实际上,这个组件都可以完成我们想要它实现的功能。 现在我们来验证一下。 

为了验证这个组件,您得先创建一个简单的工程。 我决定建一个AIR的工程来验证,这样就不用费时做web server的设置了。 从代码观点而言,一个基于web的工程几乎没什么不同,只是将WindowedApplication转换为 Application。 以下就是我主要的应用程序文件: 

1.         <?xml version="1.0" encoding="utf-8"?>
2.         <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:MXMLToAS3="com.flextras.InsideRIA.MXMLToAS3.*">
3.          
4.         <mx:VBox>
5.         <MXMLToAS3:YesNoQuestionV1 id="q1" questionText="Do you want to take a Survey?" />
6.         <mx:Text text="{q1.selectedAnswer}" />
7.         </mx:VBox>
8.          
9.         </mx:WindowedApplication>

这段代码导入了包含这个组件的包。 它创建了该组件的一个实例q1,并将问题文本声明为string类型。 这段代码包含了一个附件的文本组件实例,该实例绑到了q1的选项属性。当我们改变问题的答案时,将会看到选项属性也随之改变。 这就是运行中的应用程序:

版本2:隐藏实现

将您的代码从组件中分离出来的一个关键原因就是为了隐藏实现。若隐藏实现的话,您可以只更改实现而不用改API,而使用组件的所有代码都应该没有问题。当前的组件并没有隐藏实现细节,就如您看到的这样:问题和答案字段都是暴露在外的。通过MXML创建的话,他们都被当做公共属性。如果有人改变ComboBox的数据源( dataProvider)的话,会出现什么情况呢?您要尽量预防此类干预。 

为达到这个目的,我们打算将我们的Text以及YesNoQuestion这两个MXML组件转换为受保护的ActionScript变量,并充分利用Flex框架组件生命周期 createChildren()来创建组件并将它们加到stage中。 

在组件初始化安装的时候会调用createChildren() 方法,目的是为了创建子组件并将其加入到父容器。当处理ActionScript States时,我经常在createChildren()时初始化所有AddChild或RemoveChild state元素。我在Flextras Calendar组件实现天、周和年视图时就是这样做的。 

为了保持selectedAnswer变量与当前事件同步,我们要响应change事件并设置值。新做法将是一样的,但是我们会在ActionScript安装事件监听器和事件处理器,而不使用内置的MXML。 

首先,创建组件变量: 

1.         protected var question : Text;
2.         protected var answer : ComboBox;

因为这些是受保护的变量,意味着扩展该组件的任意组件都能访问这些变量,但是如果新组件未扩展该文件,就不能访问了。 这就是createChildren()方法: 

1.         override protected function createChildren():void{  super.createChildren();
2.          
3.               this.question = new Text();
4.               this.question.text = questionText;
5.               this.addChild(this.question);
6.          
7.               this.answer = new ComboBox();
8.               this.answer.dataProvider = this.dp;
9.               this.addChild(this.answer);
10.          this.answer.addEventListener(ListEvent.CHANGE, onChange);
11.    }

createChildren)_是在UIComponent类中初始化定义的。所有的Flex用UI组件都扩展了UIComponent,我们的也不例外,虽然我们要靠近底端一些。 为了在YesNoQuestion组件中实现这个方法,我们重写了signature方法,并调用了super()方法。 在重写方法时,调用super方法非常重要。 您永远不会知道哪些代码会在更高层次执行。 该组件创建了问题文本实例和答案ComboBox实例。 它设置了默认值并将其加入容器。 

在MXML版本中,您用了change事件来保持已选择答案的同步。而在ActionScript中,您会用相同的方法,只不过需要通过addEventListener()来设置事件监听器。它指定您要监听的事件类型及事件发布时要运行的函数,例如本例中的change事件。当我在事件类中会提到事件常量而不用事件名“change”。当然,两者都可以,只不过说常量会更灵活一些。如果事件名称变化了,我们也不用改代码。 

以下就是监听函数: 

1.         protected function onChange(e:ListEvent):void{
2.               this.selectedAnswer = this.answer.selectedItem.label;
3.         }

这个监听函数接收事件参数, 这个方法中的单行代码与之前的MXML版本使用的顺序代码是一样的。 

版本 3: commitProperties()

如果当createChildren() 被调用时,questionText仍然是空字符串,那会怎样呢? 那么问题就会什么都不显示。 在目前的代码中,questionText的属性变化时并不会更新问题。这里有一个解决方案。 Flex框架提供了commitProperties() 方法在组件的所有属性都设置好以后来运行代码。 只要值一变化,我们就使用这个方法来设置更新问题文本。 

首先,将commitProperties()方法加入代码。 我们可以将这行复制粘贴到body中以设置问题文本。 

1.         override protected function commitProperties():void{
2.               super.commitProperties();
3.               this.question.text = questionText;
4.         }

当然,这个方法也会调用它的super方法,与我们在createChildren()方法中做的类似。 Flex组件生命周期给我们提供了一个名叫 invalidateProperties()的失效方法。在组件中,我们可以随时调用这个方法,那么在下一个render事件中,组件将强制 commitProperties() 触发。 这就是 createChildren()方法和commitProperties()方法的一个不同之处.createChildren() 仅运行一次; 而commitProperties()在初始化安装期间运行一次,之后有需要的时候还可以再次调用。 

为了触发 commitProperties() 失效,我们将用get/set属性来取代questionText 的变量属性。 FlashBuilder 4包含一些代码来做这个,其中的代码类似以下代码: 

1.         private var _questionText : String;
2.         [Bindable] 
3.         public function get questionText(): String{
4.               return this._questionText;
5.         }
6.         public function set questionText(value:String):void{
7.               this._questionText = value;
8.         }

commitProperties() 方法在应用程序运行过程中运行,比我们改变questionText更为常见。 为了让commitProperties()知道它究竟需要做什么,我们需要加一个属性变化标识, 像这样: 

1.         private var questionTextChanged : Boolean = false;

set方法用来将标识设置为true,并调用invalidateProperties()方法: 

1.         public function set questionText(value:String):void{
2.               this._questionText = value;
3.               this.questionTextChanged = true;
4.               this.invalidateProperties()
5.         }

还需要重新访问commitProperties() 方法来检查标识: 

1.         override protected function commitProperties():void{
2.               super.commitProperties();
3.               if(this.questionTextChanged == true){
4.                      this.question.text = questionText;
5.                      this.questionTextChanged = false;
6.                }
7.         }

通过建立使用变量属性的selectedAnswer 方法,组件用户可以随意改变,这也会导致未意料的效果。我们可以用get方法来取代这个属性。 省去set方法的话,只能从外部读取值。这是更新过的set方法:

1.         private var _selectedAnswer : String
2.         [Bindable(event='selectedAnswerChanged')]
3.         public function get selectedAnswer (): String{
4.               return this._selectedAnswer;
5.         }

注意,我改变了Bindable元数据标签。 我增加了一个事件而没有使用它的默认状态。 当set方法存在时,Flex框架知道如何绑定属性,但是若没有set方法,则会引发警告。 这个方案用来指定绑定事件,当属性改变时,您可以自己发布。 我们为属性变化新增了一个方法:

1.         protected function setSelectedAnswer(value:String):void{
2.               this._selectedAnswer = value;
3.               this.dispatchEvent(new Event('selectedAnswerChanged'));
4.         }

属性是在onChange事件处理器中设置的。 我们必须加以更改,这样它才能取到set方法而不是直接取变量属性。

1.         protected function onChange(e:ListEvent):void{
2.         setSelectedAnswer(this.answer.selectedItem.label);
3.         }

我想指出的是,并没有相关代码能阻碍您用不同的访问控制语句来获取get和set方法。但是,ASDoc工具对此会有点儿问题,这就是我为什么刚才删除了set 和属性名之间的空格。如果您不用ASDoc的话,就放心创建公共的getter和受保护的setter吧。 

为了验证questionTx的设置,我们对主要的应用程序文件做些调整。 在q1上增加一个 TextInput 和一个 button 来修改questionText: 

1.         <mx:TextInput id="questionText" />
2.         <mx:Button click="q1.questionText = questionText.text" />

运行测试代码,您会发现我们改变问题文本一点儿问题都没有了;只读的selectedAnswer属性仍然会随着问题文本改变主要应用程序的文本组件。一切都很好。 

版本 4: 迈向完全的 ActionScript

如果您看一下您的组件代码,您会发现大部分已经是ActionScript了。 把MXML组件转化为ActionScript组件并不是一个大的跳跃。 组件从包定义开始:

package com.flextras.InsideRIA.MXMLToAS3

包定义在组件所在的文件结构中。 因此, 在YesNoQuestionV4这个应用程序中。文件存放于com目录下的Flextras 下InsideRIA目录下的MXMLToAS3。 com目录存放于主要源码根目录下。 这部分在MXML中是对我们隐藏的。 

接下来我们将类导入: 

1.         import mx.containers.HBox;
2.         import mx.controls.ComboBox;
3.         import mx.controls.Text;
4.         import mx.collections.ArrayCollection;
5.         import mx.events.ListEvent;

这些与之前的MXML版本的唯一不同在于我们导入了Hbox,我们的组件并不以此为基础。这些也同样导入到MXML组件的脚本标签中。 在 ActionScript 版本中,不导入到脚本标签,事实上ActionScript中根本就没有脚本标签。 

接下来就是类定义: 

public class YesNoQuestionV4 extends HBox

类可以与属性和方法用相同的访问控制语句。 在MXML中开发时不能指定访问控制语句 下面是类构造函数:

public function YesNoQuestionV4(){super();}

在这个例子,构造函数除了调用父类的构造函数外什么都不做。 但是,我会经常用它定义默认样式或者操作组件状态的安装。 您通常对creationComplete 事件写的任何代码十有八九都属于构造函数,然而MXML组件不支持这些构造函数。 

接下来是我们之前MXML版本中代码段中的所有ActionScript代码。在这儿我就不再复制了。 最后,方括号从类开始,到包定义结束

虽然现在您的组件已经100%都是ActionScript了,我们的主程序并不需要改变。真正实现了隐藏的话,使用您的组件的应用程序或者其他组件不关心它是ActionScript还是MXML,还是两者混合实现的。

版本5: 扩展UI组件

在之前的版本中,我们扩展了HBox类。 这让我们做起来更加容易,因为我们能使用HBox的继承能力来定位布局问题文本以及ComboBox组件。 然而,一些类中的布局算法对于您的需求来说可能过于复杂。有些时候一些简单的就能提供比较好的性能。 我们将在YesNoQuestion组件的最后一个版本中,扩展UI组件。 

第一行用来修改类定义。 之前它扩展了HBox,现在它扩展UI组件,如下: 

public class YesNoQuestionV5 extends UIComponent

这是唯一需要更改的一行代码,但是我们需要做些补充。 有两个Flex生命周期方法我们还未曾使用,那就是measure() 方法和 updateDisplayList()方法 将这两个方法实现了就可以完成我们的组件开发了。 

measure() 方法用来是为您的组件设置合理的高度和宽度,从而不会产生滚动条。 组件的父类最终为它的形状负责,因此measure() 方法其实只是设置时的参考建议,通过 measuredHeight和 measuredWidth这两个属性来实现。 

下面是这个方法: 

1.         override protected function measure():void{
2.               super.measure();
3.               this.measuredHeight = question.measuredHeight + answer.measuredHeight;
4.               this.measuredWidth = question.measuredWidth + answer.measuredWidth;
5.         }

这个方法重写了父方法,并调用了该方法的super版本。 然后它通过对每个子方法的 measuredHeight和measuredWdth 求和算出了measuredHeight。 在大部分情况下,这个计算方法只是循环了子组件,计算出了我们到此为止的近似值。 

Measure()方法可以任意设置measuredMinWidth和measuredMinHeight属性。这两个属性指定了组件最小能缩到多小程度。我并没有在这人指定值,但是经常会默认他们为100,仅仅是为了他们有个值。我已经碰到过不指定最小值的组件在使用百分比高度时的老问题。 

最后一个方法是实现 updateDisplayList()方法,updateDisplayList()方法主要用来定位子组件并设置其大小。 然而您也可以用它来完成其他显示项,例如设置样式或者用图形API画图。以下就是我们的 updateDisplayList()方法:

1.         override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
2.               this.question.setActualSize(
3.                     this.question.getExplicitOrMeasuredWidth(),
4.                     this.question.getExplicitOrMeasuredHeight());
5.          
6.               this.question.move(0,0);
7.          
8.               this.answer.setActualSize(
9.                     this.answer.getExplicitOrMeasuredWidth(),
10.                this.answer.getExplicitOrMeasuredHeight());
11.     
12.          this.answer.move(this.question.width, 0);
13.     
14.    }

updateDisplayList()方法包含在组件的unscaledWidth和the unscaledHeight这两个自变量中。实质上,这些值是您想用来设置组件的大小。为了定位组件,使用了move方法。第一个question放在左上角,x坐标和y坐标均为0.answer组件挨着第一个放置,x坐标值等于question实例的宽度,y坐标值仍然为0. setActualSize() 方法用来设置组件的大小。在这个例子中,我们用了两个方法:getExplicitoOrMeasuredWidth() 和getExplicitOrMeasuredHeight()方法。既然我们从来没有设置明确的高度或者宽度,那么就将组件设置为计算出的高度和宽度。 

最后的代码

在您开始建立组件的时候,不妨略加思考。 为了重用以及在多种场合以不同方式使用这些组件的话,您想要优化他们么? 或者这些只是你想要为您的应用程序创建的一次性组件。即使您是用ActionScript搭建的所有一切,它也不值得你花费额外的时间。 但是,尽管用了MXML组件,您仍然需要利用ActionScript技术和Flex组件生命周期方法来创建健壮的组件。 

为了保持前后一致,最终代码就会附在本文末尾 :

1.         package com.flextras.InsideRIA.MXMLToAS3
2.         {
3.               import mx.containers.HBox;
4.               import mx.controls.ComboBox;
5.               import mx.controls.Text;
6.               import mx.collections.ArrayCollection;
7.               import mx.events.ListEvent;
8.               import mx.core.UIComponent;
9.          
10.          public class YesNoQuestionV5 extends UIComponent
11.          {
12.                 public function YesNoQuestionV5()
13.                 {
14.                       super();
15.                 }
16.     
17.                 [Bindable] 
18.                 public var dp : ArrayCollection = new ArrayCollection([
19.    {label:'Yes'},
20.    {label:'No'}
21.                 ]);
22.     
23.                 private var _questionText : String;
24.                 private var questionTextChanged : Boolean = false;
25.                 [Bindable] 
26.                 public function get questionText(): String{
27.                       return this._questionText;
28.                 }
29.                 public function set questionText(value:String):void{
30.                       this._questionText = value;
31.                       this.questionTextChanged = true;
32.                       this.invalidateProperties()
33.                 }
34.     
35.                 private var _selectedAnswer : String
36.    [Bindable(event='selectedAnswerChanged')]
37.                 public function get selectedAnswer (): String{
38.                       return this._selectedAnswer;
39.                 }
40.     
41.    protected function setSelectedAnswer(value:String):void{
42.                       this._selectedAnswer = value;
43.                       this.dispatchEvent(new Event('selectedAnswerChanged'));
44.                 }
45.     
46.    protected var question : Text;
47.    protected var answer : ComboBox;
48.     
49.    override protected function commitProperties():void{
50.                       super.commitProperties();
51.                       if(this.questionTextChanged == true){
52.                             this.question.text = questionText;
53.                             this.questionTextChanged = false;
54.                       }
55.                 }
56.     
57.    override protected function createChildren():void{
58.                       super.createChildren();
59.     
60.                       this.question = new Text();
61.                       this.addChild(this.question);
62.     
63.                       this.answer = new ComboBox();
64.                       this.answer.dataProvider = this.dp;
65.                       this.addChild(this.answer);
66.                       this.answer.addEventListener(ListEvent.CHANGE, onChange);
67.                 }
68.     
69.    override protected function measure():void{
70.                       super.measure();
71.     
72.                       this.measuredHeight = this.question.measuredHeight + this.answer.measuredHeight;
73.                       this.measuredWidth = this.question.measuredWidth + this.answer.measuredWidth;
74.                 }
75.     
76.    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
77.                  this.question.setActualSize( this.question.getExplicitOrMeasuredWidth(), this.question.getExplicitOrMeasuredHeight());
78.                       this.question.move(0,0);
79.                  this.answer.setActualSize(this.answer.getExplicitOrMeasuredWidth(), this.answer.getExplicitOrMeasuredHeight());
80.                       this.answer.move(this.question.width, 0);
81.     
82.                 }
83.     
84.     
85.    protected function onChange(e:ListEvent):void{
86.    setSelectedAnswer(this.answer.selectedItem.label);
87.                 }
88.     
89.     
90.           }
91.    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值