在用 ActionScript 创建自定义组件时,必须重载 UIComponent 类的一些方法.实现基本的组件结构,构造器,以及 createChildren(),commitProperties(), measure(), layoutChrome()和 updateDisplayList()方法.
基本组件结构
下面例子展示了Flex 组件的基本结构:
1 package myComponents
2 {
3 public class MyComponent extends UIComponent
4 {
5 .
6 }
7 }
8 必需在包中定义ActionScript 自定义组件。包能够反映自定义组件在应用的路径结构中的位置.
自定义组件的类定义必须以public 关键字修饰. 尽管包含类的定义文件中可能还有其他内部类定义,但是,该文件中有且只能有一个 public 类定义.要将所有的内部类定义放在包定义的
关闭大括号之下的源文件的底部.
实现构造函器
用ActionScript写的UIComponent 类或其子类的子类,应该定义public 构造器方法.这里的构造器有以下特点:
1.没有返回类型.
2. 应被声明为public
3. 没有参数
4. 调用super()方法以使用父类的构造器.
每个类只能包含一个构造器方法;ActionScript不支持重载(overloaded)的构造方法.使用构造器可以设置类属性的初始值,比如,可以设置属性和样式的缺省值,或者初始化数据结构,比如数组. 不要在构造器中创建“子显示对象”,构造器只应用于设置组件的初始值。如果组件要创建子组件,那么可在createChildren()方法中创建。
实现createChildren()方法
在内部创建其他组件或可视化对象的组件被称为“复合组件(composite componen)” 。例如,Flex ComboBox 控件包含一个用于定义ComboBox 文本区的TextInput 控件和一个用于定义ComboBox 向下箭头的Button 控件。组件实现createChildren()方法,在其内部创建子对象(比如其他的组件) 。
应用开发者不要直接调用createChildren()方法;当开发者调用addChild()方法将组件添加到父组件中时,Flex 会自动调用createChildren()方法。注意,createChildren()没有与之相关的失效方法,这意味着组件被添加到父组件中时不会等上一会才调用这个方法。
例如,要定义一个新的组件,这个组件包含一个 Button 控件和一个 TextArea 控件,这里的Button 控件用于控制用户是否能向TextArea 控件中输入信息。下面的例子创建了 TextArea
和 Button 控件:
// Declare two variables for the component children.
private var text_mc:TextArea;
private var mode_mc:Button;
override protected function createChildren():void {
// 调用父类的 createChildren()方法.
super.createChildren();
// 在创建子组件之前检查这些子组件是否已存在
// 这是个可选项,但是这样做使得子类可以创建一个不同的子组件
if (!text_mc) {
text_mc = new TextArea();
text_mc.explicitWidth = 80;
text_mc.editable = false;
text_mc.addEventListener("change", handleChangeEvent);
// 将子组件添加到自定义组件中addChild(text_mc);
}
//在创建子组件之前检查这些子组件是否已存在.
if (!mode_mc){
mode_mc = new Button();
mode_mc.label = "Toggle Editing";
mode_mc.addEventListener("click", handleClickEvent);
//将子组件添加到自定义组件中
addChild(mode_mc);
}
}
注意,在这个例子中createChildren()方法调用addChild()来添加子组件。必须对每个子对象调用addChild()方法。在创建子对象之后,就能使用子对象的属性来定义子对象的特性。在这个例子中,我们创建了Button和TextArea控件,初始化它们,然后为它们注册事件监听器。当然,也可以给子组件添加皮肤,更完整的例子参见:例子:创建一个复合组件.
实现commitProperties()
使用commitProperties()方法来协调对组件属性的更改。绝大多数情况下,都是对影响组件如何在屏幕上显示的属性使用这个方法。
当invalidateProperties()方法调用时,Flex 会“安排(schedules) ”一个对commitProperties()方法的调用(这里的“安排(schedules)”指的不是立即执行) 。
commitProperties()方法在invalidateProperties()方法调用之后的下一个“渲染事件(render event)”中被执行。
当使用addChild()方法向容器中添加一个组件时,Flex 会自动调用invalidateProperties()方法。
commitProperties()方法的调用发生在measure()方法调用之前,这让我们能够设置measure()方法可能使用的属性值。
定义组件属性的典型模式就是用getter 和setter 方法来定义属性,如下面的例子所示:
// 为 alignText 属性定义个一个 private 变量。
private var _alignText:String = "right";
// 定义个一个标志来表明_alignText 属性是否发生了变化 private var bAlignTextChanged:Boolean = false;
// 为属性定义 getter 和 setter 方法
public function get alignText():String {
return _alignText;
}
public function set alignText(t:String):void {
_alignText = t;
bAlignTextChanged = true;
// 在需要的时候,触发 commitProperties(), measure(),和 updateDisplayList() 方法
// 在本例的中,不需要去重新度量(remeasure)组件
invalidateProperties();
invalidateDisplayList();
}
// 实现 commitProperties() 方法.
override protected function commitProperties():void {
super.commitProperties();
// 检查 flag 是否带表 alignText 属性已经变化。
if (bAlignTextChanged) {
// Reset flag.
bAlignTextChanged = false;
//处理 alignment 变化
……
……
}
}
正如这个例子中看到的那样,setter方法更改了属性,调用invalidateProperties() 和invalidateDisplayList()方法,然后返回。Setter 方法本身不执行任何基于新属性值的计算。
这种设计让 setter 方法能迅速地返回,并把对新属性值的处理留给commitProperties()方法。
改变控件中的文本(text)对齐(alignment)方式不需要改变控件的大小。但是,一旦改变了控件的大小,就要在代码加入对invalidateSize()方法的调用,以触发measure()方法。
使用commitProperties()方法的优点如下:
1.能协调对多个属性的修改,使得这些变更能够同时生效。
例如,可能定义多个属性来控件组件文本的显示,比如,文本在组件内部的对齐(alignment)属性。Text或者alignment属性的变化都需要Flex 去更新组件的显示。但是,如果text和alignment 都被改变了,在屏幕更新时,你会希望 Flex 能够一次性地执行所有的有关大小和位置的计算。
因此,需要使用commitProperties()方法来计算所有与其它属性相关的属性值。通过commitProperties ()方法来协调属性的变更,可以减少不必要的重复处理。
2. 能够协对同一个属性的多次修改。
这样就不必每次更新组件的一个属性都执行复杂的计算。比如,用户更改Button控件的icon属性以更改Button控件上显示的图片。根据icon 的大小或百分比计算label 的位置是一个开销较大的操作,这样的操作应只在必要时执行一次。
为了避免这样的行为,要使用commitProperties()方法去执行计算。 当更新显示时Flex会调用commitProperties()方法。 这意味着不论两次屏幕更新之间属性曾经变化了多少次,Flex 只在屏幕更新时执行一次计算。
下面的例子显示在 commitProperties()方法中如何处理两个相关属性:
//为 text属性定义一个 private变量.
private var _text:String = "ModalText";
private var bTextChanged:Boolean = false;
//定义 getter 方法.
public function get text():String {
return _text;
}
//定义 setter 方法以便在属性变化时调用 invalidateProperties()
public function set text(t:String):void {
_text = t;
bTextChanged = true;
invalidateProperties();
// 改变 text 属性导致控件重新计算缺省大小 invalidateSize();
invalidateDisplayList();
}
//为 alignText 属性定义一个 private 变量。
private var _alignText:String = "right";
private var bAlignTextChanged:Boolean = false;
public function get alignText():String
{
return _alignText;
}
public function set alignText(t:String):void {
_alignText = t;
bAlignTextChanged = true;
invalidateProperties();
invalidateDisplayList();
}
// 实现 commitProperties() 方法.
override protected function commitProperties():void {
super.commitProperties();
//检查两个属性是否发生变化的标志
if (bTextChanged && bAlignTextChanged)
{
//重置标志
bTextChanged = false;
bAlignTextChanged = false;
//处理两个属性都发生变化的情况
}
//判断是否 text 属性发生变化
if (bTextChanged) {
// 重置属性。
bTextChanged = false;
// 处理 text 属性的变化。
}
// 检查 alignText 属性是否变化
if (bAlignTextChanged) {
//重置属性.
bAlignTextChanged = false;
// 处理 alignment 属性的变化.
}
}
实现measure()方法
measure()方法设置组件的缺省大小,以像素为单位,并且也可以有选择性地设置组件其他属性的缺省值。
当invalidateSize()方法的调用发生后,Flex 会“安排”一个对measure()方法的调用。measure()方法在invalidateSize()调用之后的下一个“渲染事件(render event)”时执行。
当使用addChild()方法将组件添加到容器中时,Flex 会自动调用invalidateSize()方法。
当为组件设置特定的高和宽后,尽管显示地调用 invalidateSize()方法,但 Flex 不会调用measure()方法。也就是说,只有当组件的 explicitWidth 和 explicitHeight 属性是 NaN 时 Flex
调用measure()方法。 在下面的例子中,由于已经显式地设置了Button 控件的大小,Flex 不会调用Button.measure()方法:
<mx:Button height="10" width="10"/>
在已有组件的子类中,只有当正在执行的动作需要更改父类中定义的组件大小设定规则时,才会实现measure()。因此,要设置一个新的缺省值,或者在运行时执行计算以确定组件大小的
规则,就要实现measure()方法。在measure()方法中设置以下有关组建大小的缺省:
measuredHeight,measuredWidth 以像素为单位设定组件的缺省高度和宽度。 这些属性被设置为0,直到 measure()方法被执行。使它们设置为0,使得组件在缺省情况下不可见。
measuredMinHeight ,measuredMinWidth 指定组件缺省的最小高度和最小宽度,以像素为单位。Flex不能将组件的大小设置为比指定的最小值还小。
measure()只设置组件的缺省大小。在 updateDisplayList()方法中,组件的父容器将其实际大小传递给组件,这些属性值与缺省值不同。
组件开发者在应用中用以下列方式也能重载组件的缺省大小:
1, 设置 explicitHeight 和 exlicitWidth 属性。
2, 设置 width 和 height 属性。
3, 设置 percentHeight 和 percentWidth 属性。
例如,定义一个 Button 控件,其缺省的大小为 100 像素宽,50 像素高,并且缺省的最小值为 50 像素宽,25 像素高,如下例所示:
package myComponents
{
// asAdvanced/myComponents/DeleteTextArea.as
import mx.controls.Button;
public class BlueButton extends Button {
public function BlueButton() {
super();
}
override protected function measure():void {
super.measure();
measuredWidth=100;
measuredMinWidth=50;
measuredHeight=50;
measuredMinHeight=25;
}
}
}
下面的应用中使用了这个 button。
<?xml version="1.0"?>
<!-- asAdvanced/ASAdvancedMainBlueButton.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:MyComp="myComponents.*" >
<mx:VBox>
<MyComp:BlueButton/>
<mx:Button/>
</mx:VBox>
</mx:Application>
Button 上没有设置任何其它有关 button 大小的约束,VBox 使用 button 的缺省大小,和缺省的最小大小来计算 VBox 在运行时的大小。也可以在应用中重载缺省的大小设置:
<?xml version="1.0"?>
<!-- asAdvanced/MainBlueButtonResize.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:MyComp="myComponents.*" >
<mx:VBox>
<MyComp:BlueButton width="50%"/>
<mx:Button/>
</mx:VBox>
</mx:Application>
在这个例子中,制定 button 的宽度是 VBox 容器宽度的 50%。当容器宽度的 50%小于 button的最小宽度时,button 使用它的最小宽度。
计算缺省大小
上例子中, 实现 measure()方法时用静态的值设置缺省的大小和缺省的最小大小。 一些Flex组件使用了静态大小,比如TextArea 的静态大小为 100 像素宽,44像素高,而不管它所包含的文本什么样。如果文本比 TextArea 控件大,控件就显示滚动条。(译者注:这里的静态应该指的是绝对布局下的表示像素个数的高宽值,而不是百分比 ).
通常,根据组件特点或者传递给该组件的信息来设置它的缺省大小。比如,Button 控件的measure()检查它的标签文本,补白(margin)以及字体的特性来决定组件的缺省大小。
在下面的例子中,重载了 TextArea 控件的 measure()方法,这样它就能够检测传递给控件的文本,以及计算 TextArea 控件的缺省大小,以使它能在一行中显示整个文本字符串:
package myComponents
{
// asAdvanced/myComponents/MyTextArea.as
import mx.controls.TextArea;
import flash.text.TextLineMetrics;
public class MyTextArea extends TextArea
{
public function MyTextArea() {
super();
}
// The default size is the size of the text plus a 10 pixel margin.
override protected function measure():void {
super.measure();
// Calculate the default size of the control based on the
// contents of the TextArea.text property.
var lineMetrics:TextLineMetrics = measureText(text); // Add a 10 pixel border area around the text.
measuredWidth = measuredMinWidth = lineMetrics.width + 10;
measuredHeight = measuredMinHeight = lineMetrics.height + 10;
}
}
}
当 text 字符串长度超过应用的显示区域,通过增加逻辑来增长 TextArea 控件的高度,使文本( text) 能在多行显示。下面应用使用了这个组件:
<?xml version="1.0"?>
<!-- asAdvanced/MainMyTextArea.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:MyComp="myComponents.*" >
<MyComp:MyTextArea id="myTA" text="This is a long text strring that would normally cause a TextArea control to display scroll bars. But, the custom MyTextArea control calcualtes its default size based on the text size."/>
<mx:TextArea id="flexTA" text="This is a long text strring that would normally cause a TextArea control to display scroll bars. But, the custom MyTextArea control calcualtes its default size based on the text size."/>
</mx:Application>
实现 layoutChrome()方法
Container 类,以及 Container 类的子类,使用layoutChrome()方法来定义容器区域的边框(border area) 。
当invalidateDisplayList()方法调用发生时,Flex 将“安排”一个 layoutChrome()方法的调用。layoutChrome()方法在invalidateDisplayList()方法调用之后的下一个“渲染事件”
期间执行。当使用 addChild()方法将一个组件添加到容器中时,Flex 自动调用invalidateDisplayList()方法。通常,使用 RectangularBorder 类来定义容器区域的边框。比如,可以创建一个RectangularBorder 对象,然后在重载的 createChildren()方中,将其作为一个子控件添加到组件中。
当创建容器类的子类时, 可以使用 createChildren()方法去创建容器的 “内容子控件”。“内容子控件”是指在容器中显示的子组件。用 updateDisplayList()方法来确定“内容子控件”的位置。
使用 layoutChrome()方法通常是用于定义容器的边框区域和确定边框区域的位置,以及确定要在边框区域中显示的附加元素。例如,Panel 容器使用 layoutChrome()方法定义 panel 容器的title 区域,这个区域用来包含title文本和close按钮。 将容器的内容区域和容器边框区域分开处理的主要原因是为了处理Container.autoLayout属性被设置为false的这种情况。当autoLayout(自动布局)属性被设置为true的时候,只要容器子控件的大小和位置发生变化,容器及其子控件就会进行度量和布局。缺省值为 true。
当 autoLayout 属性被设置为 false 的时候,度量和布局只在子控件被添加到容器中或者从容器中移出时执行。 然而, Flex 在两种情况下都执行 layoutChrome()。 所以, 就算在autoLayout属性被设置为 false 的情况下,容器仍然能够更新它的边框区域。
实现 updateDisplayList()方法
updateDisplayList()方法按照前面被调用的方法中设定的属性和样式来设定子组件的大小和位置,并画出组件所使用的皮肤和图片元素。
而组件本身的大小由组件的父容器决定。
直到组件的 updateDisplayList()方法被调用之后,组件才能在屏幕上显示出来。当invalidateDisplayList()方法调用发生时,Flex 会“安排”一个对 updateDisplayList()方法
的调用。updateDisplayList()方法在invalidateDisplayList()方法调用之后的下一个“渲染事件”发生时才会被执行。当使用addChild()方法将组件添加到容器中时,Flex会自动调用
invalidateDisplayList()方法。
updateDisplayList()方法的主要用途如下:
1,用于设置组件中元素的大小和位置,以用于组件的显示。很多组件由一个或者多个子组件组成,或者有若干属性用于控制组件中信息的显示。比如,Button 控件有一个可选的 icon,并且,使用 labelPlacement 属性可以指定按钮上的文字在相对于icon 的什么地方显示(左侧还是右侧) 。
Button.updateDisplayList()方法使用 icon 和 labelPlacement 属性值来控制button 的显示。
对于有子组件的容器来说,updateDisplayList()方法控制那些子组件该如何确定位置。比如,Hbox 容器的 updateDisplayList()方法在一行上按照从左到右的循序确定子组件的位置。VBox 容器的 updateDisplayList()方法在一列上按照从上到下的顺序确定子组件的位置。
要在 updateDisplayList()方法中确定一个组件的大小,应当使用 setActualSize()方法,而不是使用与组件大小相关的属性,诸如 width 和 height。要确定组件的位置,应当使用 move()方法,而不是x和y 属性。
2,用于画出组件所需的所有可视元素。
组件支持很多类型的可视元素,比如皮肤,样式,和边框。在 updateDisplayList()方法中,可以添加这些可视元素,使用 Flash 绘画 APIs,以及对组件中这些可视化的显示
执行另外一些控制。
updateDisplayList()方法形式如下:
protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
属性有以下值:
unscaledWidth 指定组件的宽度,以像素为单位,在组件的坐标系中,不管组件的 scaleX 属性值是多少。这个值就是由父容器所确定的组件宽度。
unscaledHeight 指定组件的高度,以像素为单位,在组件的坐标系中。不管组件的 scaleY 属性值是多少。
这个值就是由父容器所确定的组件高度。 缩放发生在 Flash Player 或者 AIR 中,发生时机是在 updateDisplayList()执行之后。比如, 一个组件的 unscaledHeight 属性是 100, 而其 scaleY 属性是 2.0,那么它在 Flash Player 或 AIR 中出现的高度为 200 像素。
VBox 容器对布局机制的重载(override) :
VBox 容器按照子组件加入到容器中的先后顺序将子组件按照从上到下的方式进行布局。下面的例子重载了它的 updateDisplayList()方法,这个方法使 VBox 容器按照从底向上的方式进
行布局:
package myComponents
{
// asAdvanced/myComponents/BottomUpVBox.as import mx.containers.VBox;
import mx.core.EdgeMetrics;
import mx.core.UIComponent;
public class BottomUpVBox extends VBox
{
public function BottomUpVBox() {
super();
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Get information about the container border area.
// The usable area of the container for its children is the
// container size, minus any border areas.
var vm:EdgeMetrics = viewMetricsAndPadding;
// Get the setting for the vertical gap between children.
var gap:Number = getStyle("verticalGap");
// Determine the y coordinate of the bottom of the usable area
// of the VBox.
var yOfComp:Number = unscaledHeight-vm.bottom;
// Temp variable for a container child.
var obj:UIComponent;
for (var i:int = 0; i < numChildren; i++)
{
// Get the first container child.
obj = UIComponent(getChildAt(i));
// Determine the y coordinate of the child.
yOfComp = yOfComp - obj.height;
// Set the x and y coordinate of the child.
// Note that you do not change the x coordinate.
obj.move(obj.x, yOfComp);
// Save the y coordinate of the child,
// plus the vertical gap between children.
// This is used to calculate the coordinate
// of the next child.
yOfComp = yOfComp - gap;
}
}
}
}
在这个例子中,使用 UIComponent.move()方法设置容器中每个子控件的位置。也可以用UIComponent.x 及 UIComponent.y 属性去设置这些坐标。区别就是,move()方法不仅改变组件的位置, 而且在调用这个方法之后立即分发了一个 move 事件, 设置x和y 属性也更改组件的位置,但却在下一个屏幕更新事件中分发 move 事件。
下面的应用使用了这个组件:
<?xml version="1.0"?>
<!-- asAdvanced/MainBottomVBox.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:MyComp="myComponents.*" >
<MyComp:BottomUpVBox>
<mx:Label text="Label 1"/>
<mx:Button label="Button 1"/>
<mx:Label text="Label 2"/>
<mx:Button label="Button 2"/>
<mx:Label text="Label 3"/>
<mx:Button label="Button 3"/>
<mx:Label text="Label 4"/>
<mx:Button label="Button 4"/>
</MyComp:BottomUpVBox>
</mx:Application>
在组件中画图
每个 Flex 组件都是 Flash Sprite 类的子类,并因此而继承了 Sprite.graphics 属性。Sprite.graphics 属性所指定的 Graphics 对象可以用来向组件中添加矢量绘画(vector
drawings)。
例如,在 updateDisplayList()方法中,可以使用 Graphics 类去画出边框和水平线以及其他图形元素:
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Draw a simple border around the child components.
graphics.lineStyle(1, 0x000000, 1.0);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
}