通常用继承已有类的方式来创建组件。比如,要创建基于Button的控件,我们就创建mx.controls.Button类的一个子类。如果要创建全新的组件,则需要创建mx.core.UIComponent类的子类。
重载UIComponent类的protected方法
Flex所有的可视化组件都是UIComponent类的子类。因此,可视化组件继承了UIComponent类所定义的方法(methods)、属性(properties)、事件(events)、样式(styles)和效果(effects)。要创建自定义的可视化组件,必须实现一个构造器(constructor),另外要有选择性地重载UIComponent类中的一个或多个protected方法。下表列出了自定义组件需要重载的protected方法。从前面的内容我们可以知道,组件的使用者不会直接调用所有这些方法,Flex框架会自动调用这些方法。
创建组件的步骤
为了实现一个组件,就要重载组件的方法,定义新的属性,分发新的事件,或者执行其他任何应用所需的自定义的逻辑。要想实现自己的组件,应遵循以下这些常规步骤:
从一个基类扩展,比如UIComponent或者其他的组件类。
指定使用者能够通过MXML标记进行设置的属性。
嵌入所有的图片和皮肤文件。
实现构造器。
实现UIComponent.createChildren()方法。
实现UIComponent.commitProperties()方法。
实现UIComponent.measure()方法。
实现UIComponent.layoutChrome()方法。
实现UIComponent.updateDisplayList()方法。
增加属性、方法、样式、事件以及元数据。
自定义组件的实现
在用ActionScript创建自定义组件时必须重载UIComponent类的一些方法,实现基本的组件结构和构造器以及createChildren()、commitProperties()、 measure()、 layoutChrome()和 updateDisplayList()方法。
1. 基本结构
2. 实现构造器
3. 实现createChildren()方法
在内部创建其他组件或可视化对象的组件被称为“复合组件”(composite component)。例如,Flex中的ComboBox控件包含一个用于定义ComboBox文本区的TextInput控件和一个用于定义ComboBox向下箭头的Button控件。自定义组件通过实现createChildren()方法来创建其内部对象。
应用开发者(非组件开发者)不要直接调用createChildren()方法。了解组件生命周期后我们知道:当开发者调用addChild()方法将组件添加到父容器中时,Flex会自动调用子组件的createChildren()方法。
package com.st.sample { import flash.events.Event; import flash.events.MouseEvent; import mx.controls.Button; import mx.controls.TextArea; import mx.core.UIComponent; public class MyComponet extends UIComponent { private var text_mc:TextArea; private var mode_mc:Button; public function MyComponet() { super(); } override protected function createChildren():void { 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(MouseEvent.CLICK, handleClickEvent); addChild(mode_mc); } } private function handleClickEvent(event:MouseEvent):void { } private function handleChangeEvent(event:Event):void { } } }
4. 实现commitProperties()方法
使用commitProperties()方法来协调对组件属性的更改。绝大多数情况下,都是对那些会影响组件在屏幕上显示的属性使用该方法。当invalidateProperties()方法被调用后,commitProperties()方法会在invalidateProperties()方法调用之后的下一个“渲染事件(render event)”中被执行。当使用addChild()方法向容器中添加一个组件时,Flex会自动调用invalidateProperties()方法。commitProperties()方法的调用发生在measure()方法调用之前,这使我们能够设置measure()方法可能会使用到的属性值。
5. 实现measure()方法
6. 实现layoutChrome()方法
7. 实现updateDisplayList()方法
updateDisplayList()方法按照布局的提交和度量阶段所设定的属性和样式来设定子组件的大小和位置,并画出组件所使用的皮肤和图片元素,而组件本身大小则由组件的父容器所决定。
直到组件的updateDisplayList()方法被调用之后,组件才能在屏幕上显示出来。当invalidateDisplayList()方法调用后,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属性。当然使用width和height属性不会出现问题,就是使组件在进入下一帧时需要重画。有些时候,对于容器组件,如果不太熟悉其边饰的画法,使用width和height属性会比使用setActualSize()方法好一些,因为使用width和height也会导致边饰重画,因此开发者可以偷懒,而不需要重载layoutChrome()方法处理容器的边饰。
2.用于画出组件所需的所有可视元素。组件支持很多类型的可视元素,比如皮肤、样式和边框。在updateDisplayList()方法中,可以添加这些可视元素或者使用Flash绘图API画出这些可视元素。
updateDisplayList()方法的形式如下:
protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
VBox容器按照子组件加入到容器中的先后顺序将子组件按照从上到下的方式进行布局。代码清单2-29中的例子重载了它的updateDisplayList()方法,这个方法使VBox容器按照从底向上的方式进行布局,如下所示。
package com.st.sample { import mx.containers.VBox; import mx.core.EdgeMetrics; import mx.core.UIComponent; public class MyVBox extends VBox { public function MyVBox() { super(); } override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); //EdgeMetrics 类可指定可视组件周围四个边缘区域的粗细(以像素为单位)。 var vm:EdgeMetrics = viewMetricsAndPadding; //获取子组件在竖直方向的间隙 var gap:Number = getStyle("verticalGap"); //确定VBox容器子组件可用区域的y坐标 var yOfComp:Number = unscaledHeight - vm.bottom; var obj:UIComponent; for(var i:int = 0; i < numChildren; i++) { //获取容器的子组件 obj = UIComponent(getChildAt(i)); // yOfComp = yOfComp - obj.height; obj.move(obj.x, yOfComp); yOfComp = yOfComp - gap; } } } }
每个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); graphics.lineStyle(1, 0x000000, 1.0); graphics.drawRect(0, 0, unscaledWidth, unscaledHeight); }