自定义组件开发 第三节   ActionScript组件开发
        

本节重点介绍了如何开发 Flex 可视组件。通过对 UIComponent 中各种与组件开发有关的方法的学习,我们将了解到 Flex 组件的基本结构,了解 Flex 组件开发中所蕴藏的秘密。最后本节以一个 ActionScript 组合组件的开发为例,向大家展示具体是如何实现自定义 Flex 可视组件的。

 

 

可视组件的基本结构

 

        所有的 Flex 可视组件都是 UIComponent 类的子类。因此,可视组件将继承 UIComponent 中定义的方法、属性、样式、事件和特效。为了创建一个可视组件,我们必须实现类的构造函数。此外,我们还可以有选择的重写一个或多个下列 UIComponent 中定义的保护方法。

 

UIComponent 方法 说明
commitProperties() 提交组件属性的任何变化,或者确认属性的变化是同时发生的,或者确保属性值按一定顺序设置的。
createChildren() 创建组件的子组件。例如,在 ComboBox 组件里包含一个 TextInput 控件以及一个 Button 控件作为其子组件。
layoutChrome() 为容器类的子类定义容器周围的边框区域。
measure() 设置组件的默认大小及默认的最小尺寸。
updateDisplayList() 根据组件的属性和样式,设置组件子元素的大小和位置,绘制组件使用的皮肤或者图形元素。组件父容器的大小决定了组件的大小。

        组件的使用者并不会直接调用这些方法。这些方法将作为初始化过程的一部分,在创建组件时被 Flex 所调用,或者在调用其他方法时被调用。

 

 

综上所述,Flex 中,一个可视组件的基本结构如下:

package com.custom.controls
{ 
public class VisualComponent extends UIComponent
{        
    ////////////////////////////////////////
    // 构造函数 
    ////////////////////////////////////////
    public function VisualComponent()
    {
    }
 
    ////////////////////////////////////////
    // 重写 createChildren 方法 [可选] 
    ////////////////////////////////////////
    override protected function createChildren():void
    {
    }
 
    ////////////////////////////////////////
    // 重写 commitProperties 方法 [可选] 
    ////////////////////////////////////////
    override protected function commitProperties():void
    {
    }
 
    ////////////////////////////////////////
    // 重写 measure 方法 [可选] 
    ////////////////////////////////////////
    override protected function measure ():void
    {
    }
 
    ////////////////////////////////////////
    // 重写 layoutChrome 方法 [可选] 
    ////////////////////////////////////////
    override protected function layoutChrome():void
    {
    }
 
    ////////////////////////////////////////
    // 重写 updateDisplayList 方法 [可选] 
    ////////////////////////////////////////
    override protected function updateDisplayList(unscaledWidth:NumberunscaledHeight:Number ):void
    {
    }
} 

}

        我们必须将 ActionScript 自定义组件定义在包中。包反映了应用程序目录结构中组件所在目录的位置。示例中的类,表明在应用程序根目录下的 com/custom/controls/ 目录中,以 ActionScript 文件的形式定义了 VisualComponent 组件,其文件名为 VisualComponent.as。 其中,包 com.custom.controls 对应目录结构 com/custom/controls/。

        组件的类定义必须以 public 关键字为前缀。如果以 internal 为关键字,则该类对包外的类是不可见的,即我们无法在包外使用该类。虽然一个 ActionScript 文件中能定义多个 internal 类,但是能定义且只能定义一个 public 类。 任何 internal 类的定义都必须放在包定义的最后一个花括号之后。下面代码演示了在目录结构com/custon/controls/所含的文件 VisualComponent.as 中定义了一个 public 类和两个 internal 类。

package com.custom.controls

{

import mx.core.UIComponent;

 

public  class VisualComponent extends UIComponent

{       

}

}

 

 

internal class SampleClass1

{

}

 

internal class SampleClass2

{

}

失效验证机制

        在组件未被销毁期间,应用程序可能通过,改变组件的大小和位置、更改控制组件显示的某个属性、更改组件的某个样式或者皮肤属性,来更改组件。例如,我们可能更改组件中显示的文本的字体大小。因为字体大小的改变,组件的大小也可能需要改变,这需要 Flex 更新整个应用程序的布局。布局操作可能需要 Flex 调用组件的 commitProperties()、measure()、layoutChrome() 以及 updateDisplayList() 方法。

        我们的应用程序能够以编程方式改变组件的字体大小,这比 Flex 更新整个应用程序的布局要快得多。因此,我们应该在确定了最终的字体大小之后再来更新整个应用程序的布局。

        在另一个情景下,当我们设置组件的多个属性的时候,例如设置 Button 控件的 label 和 icon 属性,我们希望所有属性都设置完毕后,commitProperties()、measure() 以及 updateDisplayList() 方法只执行一次。我们不希望在设置 label 属性的时候这些方法执行一次,在设置 icon 属性的时候,这些方法又执行一次。

        同样的,一些组件可能同时改变他们的字体大小。我们希望 Flex 调整布局操作以去除多余的处理,而不是在每个组件改变其字体大小后都重新布局一次。

        Flex 使用失效验证机制来同步组件的更改。Flex 使用一系列方法来实现这种机制。通过调用这些方法,Flex 可以发出信号,表明组件发生了改变,需要调用组件的 commitProperties()、measure()、layoutChrome() 或者 updateDisplayList() 方法。

下面的表格描述了这些失效验证方法:

 

失效验证方法 说明
invalidateProperties() 标记组件以使组件的 commitProperties() 方法在下次屏幕刷新期间被调用。
invalidateSize() 标记组件以使组件的 measure() 方法在下次屏幕刷新期间被调用。
invalidateDisplayList() 标记组件以使组件的 layoutChrome() 及 updateDisplayList() 方法在下次屏幕刷新期间被调用。

        当组件调用失效验证方法的时候,它向 Flex 发出信号表明组件必须被更新。当多个组件调用失效验证方法的时候,Flex 调整所有更新,使它们在下次屏幕更新期间一起发生。

通常情况下,组件使用者并不直接调用失效验证方法,而是在必要的时候,通过组件的 setter 方法或者组件的其他方法来调用。

 

基本结构详解

 

构造函数

如果一个 ActionScript 类的父类是 UIComponent,或者是UIComponent 的某个子类,那么该类应该定义一个 public 的构造函数,且该构造函数有如下特点:

每个类只能有一个构造函数;ActionScript 不支持重载构造函数。

在构造函数中可以初始化类属性的值,例如,我们可以设置属性和样式的默认值,或者初始化数据结构,例如数组。

不要在构造函数中创建子组件;应该只在构造函数中初始化组件的属性。如果需要创建子组件,在 createChildren() 方法中创建他们。

 

createChildren() 方法

        如果一个组件中创建了其他组件或者可视对象,那么该组件可以被称为组合组件。例如,Flex ComboBox 控件包含一个 TextInput 控件来定义 ComboBox 的文本区域,以及一个 Button 控件来定义 ComboBox 的箭头。组件通过实现 createChildren() 方法来创建子对象(例如其他组件)。

        我们不会直接调用 createChildren() 方法,当我们调用 addChild(),将组件加入到其父对象之中的时候,Flex 会调用该方法。createChildren() 方法没有对用的失效方法,这意味着在我们将组件添加到其父对象之中后,我们不需要再次调用该方法(子组件作为组件的一部分,在组件未被销毁期间都是存在的,因此不需要多次创建)。

 

commitProperties() 方法

        我们使用 commitProperties() 方法来协调对组件属性的更改,大多数情况下,我们协调的是影响到组件如何在屏幕上显示的属性。

        当调用 invalidateProperties() 方法的时候,Flex 会安排一个对 commitProperties() 方法的调用。当我们使用 addChild() 方法将一个组件添加到一个容器中的时候,Flex 会自动调用 invalidateProperties() 方法。

        对 commitProperties() 方法的调用在 measure() 方法之前,这使得我们可以在 commitProperties() 方法中预先设置 measure() 方法中可能用到的属性的值。

使用 commitProperties() 方法的主要优点在于:

 

measure() 方法

        measure() 方法用来设置默认的组件大小,以像素为单位,我们也可以在这里设置组件默认的最小尺寸。

        当我们调用 invalidateSize() 方法的时候,Flex 会安排一个对 measure() 方法的调用。在调用了 invalidateSize() 方法之后,measure() 方法将在下一次渲染界面的时候执行。当我们使用 addChild() 方法将组件添加到容器中的时候,Flex 会自动调用 invalidateSize() 方法。

        当我们给组件设定高度和宽度的时候,Flex 不会调用 measure() 方法,即使我们显式的调用 invalidateSize() 方法。这是因为,Flex 只在 explicitWidth 属性或者 explicitHeight 属性为 NaN 的时候才调用 measure() 方法。

        下面的示例中,因为我们显式的设置了 Button 控件的大小,Flex 不会调用 measure() 方法:

 

<mx:Button height="10" width="10"/>

 

        在现有组件的子类中,只有当我们需要修改超类中设置组件大小的规则的时候,我们才实现 measure() 方法。因此,为了设置新的默认大小,或者为了在运行时执行运算来决定设置组件大小的规则,我们需要重写 measure() 方法。

我们通过在 measure() 方法中设定下列属性的值,来设置组件的默认大小:

 

属性 说明
measuredHeight
measureWidth
指定组件默认的高度和宽度,以像素为单位。在 measure() 方法被调用之前,这些属性的值都为0。虽然我们可以保持这些属性值都为0,但是这样会使组件在默认情况下不可见。
measuredMinHeight
measureMinWidth
指定组件默认的最小高度和最小宽度,以像素为单位。Flex 不能将组件的大小设置的比其指定的最小尺寸还小。

        measure() 方法仅能够设置组件的默认大小。在 updateDisplayList() 方法中,组件的父容器将指定组件的真实大小,这可能与其默认尺寸不相同。

组件使用者在应用程序中可以通过下列方式来重写组件默认大小的设置:

 

        如何计算默认大小呢?一些 Flex 组件使用静态的大小。例如,无论TextArea 控件里面包含什么样的文本,它的默认大小都为100像素宽、44像素高。如果文本所占的空间比 TextArea 控件大,TextArea 控件上就会显示出滚动条来。

        通常情况下,我们根据组件的特征或者传递给组件的信息来设置组件的大小。例如,Button 控件的 measure() 方法检查其 label 的文本、边界设置和字体特点来决定其默认大小。

 

layoutChrome() 方法

 

        Container 类,以及一些 Container 类的子类,使用 layoutChrome() 方法来定义容器周围的边框区域。

        当我们调用 invalidateDisplayList() 方法的时候,Flex 会安排一个对 layoutChrome() 方法的调用。在调用了 invalidateDisplayList() 方法之后,layoutChrome() 方法将在下一次渲染界面的时候执行。当我们使用 addChild() 方法将组件添加到容器中的时候,Flex 会自动调用 invalidateDisplayList() 方法。

        通常情况下,我们使用 RectangularBorder 类来定义容器的边框区域。例如,我们可以创建一个 RectangularBorder 对象,然后在重写的 createChildren() 方法中将其作为子组件添加到组件中。

        当我们创建 Container 类的子类的时候,我们可以使用 createChildren() 方法来创建组件的子内容;子内容是容器中容纳的组件。然后我们可以在 updateDisplayList() 方法中设置子内容的位置。

        通常情况下我们使用 layoutChrome() 方法定义容器的边框区域及设置边框区域的位置,还可以添加其他任何希望出现在边框区域的元素。例如,Panel 容器使用 layoutChrome() 方法来定义其标题区域,其中包括标题的文本和关闭按钮。

        将容器的内容区域和边框区域分开处理的主要原因在于为了处理 Container.autoLayout 属性被设置为 false 的情况。当 autoLayout 属性被设置为 true,无论何时,只要容器里面某个子内容的大小或者位置发生了改变,Flex 都会重新计算容器和所有子内容的大小及位置。autoLayout 属性的默认值为 true。当 autoLayout 属性被设为 false,容器及其子内容的大小或位置只会在添加子内容或者移除子内容的时候计算一次。然而,两种情况下,Flex 都会执行 layoutChrome() 方法。因此,即使 autoLayout 属性被设为 false,容器依然可以刷新其边框区域。

 

updateDisplayList() 方法

 

        组件的 updateDisplayList() 方法根据前面指定的属性和样式来设置组件的子对象的大小及位置,绘制组件中使用的任何皮肤及图形元素。组件的父容器负责指定组件自身的大小和位置(例如,在容器类的 updateDisplayList() 方法中设置其子内容的大小和位置)。

        直到其 updateDisplayList() 方法被调用,组件是不会显示在屏幕上的。当我们调用 invalidateDisplayList() 方法的时候,Flex 会安排一个对 updateDisplayList() 方法的调用。在调用了 invalidateDisplayList() 方法之后,updateDisplayList() 方法将在下一次渲染界面的时候执行。当我们使用 addChild() 方法将组件添加到容器中的时候,Flex 会自动调用 invalidateDisplayList() 方法。

updateDisplayList() 方法的主要用途如下:

updateDisplayList() 方法有如下签名:

 

protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void

unscaledWidth 在组件自身的坐标系中,以像素为单位,指定了组件的宽度,与组件的 scaleX 属性无关。这是由组件的父容器决定的宽度。

 

unscaledHeight 在组件自身的坐标系中,以像素为单位,指定了组件的高度,与组件的 scaleY 属性无关。这是由组件的父容器决定的高度。

        缩放发生在 Flash Player 或者 AIR 中,在 updateDisplayList() 方法执行之后。例如,一个组件的 unscaledWidth 为 100,它的 scaleY 为2.0,则在 Flash Player 或者 AIR 中,它显示出来的宽度为200.

        每一个 Flex 可视组件都是 Flash Sprite 类的子类,因此它们继承了 Sprite.graphics 属性。Sprite.graphics 属性指定了一个 Graphics 对象。我们可以使用这个对象在组件中绘制矢量图形。

 

初始化生命周期

        组件初始化生命周期描述了我们从一个组件类创建组件对象的时候产生的一系列步骤。作为初始化生命周期的一部分,Flex 自动调用组件的方法、派发事件以及使组件可见。

        下面的示例用 ActionScript 创建了一个 Button 控件并将其添加到一个容器中:

// 创建 Box 容器。

var boxContainer:Box = new Box();

// 配置 Box 容器。

 

// 创建 Button 控件。

var b:Button = new Button()

// 配置 Button 控件。

b.label = "Submit";

...

// Button 控件添加到 Box 容器中。

boxContainer.addChild(b);

下面的步骤显示了当我们执行代码创建 Button 控件并将其添加到容器中时发生了什么:

  1. 调用组件的构造函数。

// 创建 Button 控件。

var b:Button = new Button()

      2. 通过设置组件的属性来配置组件。

// 配置 Button 控件。

b.label = "Submit";

      3. 组件的 setter 方法可能调用 invalidateProperties()、invalidateSize() 或者 invalidateDisplayList() 方法。

        我们调用 addChild() 方法将组件添加到容器中去。

// Button 控件添加到 Box 容器中。

boxContainer.addChild(b);

  1. Flex 将执行以下操作:

  2. 设置组件的 parent 属性来引用其父容器。
  3. 计算组件的样式设置。
  4. 在组件上派发 preinitialize 事件。
  5. 调用组件的 createChildren() 方法。
  6. 调用 invalidateProperties()、invalidateSize() 和 invalidateDisplayList() 方法来引起随后在下次渲染界面期间对 commitProperties()、measure() 或者 updateDisplayList() 方法的调用。

    唯一例外的是当用户设置了组件的高度和宽度的时候,Flex 不会调用 measure() 方法。

  7. 在组件上派发 initialize 事件。这个时候,该组件所有的子对象都已经被初始化,但是组件本身并没有因为布局而被设置大小或者处理。我们可以在组件被计算进布局之前使用该事件执行额外的处理。
  8. 在父容器上派发 childAdd 事件。
  9. 在父容器上派发 initialize 事件。
  10. 在下次渲染界面期间,Flex 将执行以下操作:
    a.调用组件的 commitProperties() 方法。
    b.调用组件的 measure() 方法。
    c.调用组件的 layoutChrome() 方法。
    d.调用组件的 updateDisplayList() 方法。
    e.在组件上派发 updateComplete 事件。
  11. 如果 commitProperties()、measure() 或者 updateDisplayList() 方法调用了 invalidateProperties()、invalidateSize() 或者 invalidateDisplayList() 方法,Flex 将派发额外的渲染事件。
  12. 最后一个渲染事件发生了之后,Flex 将执行以下操作:
    a.通过设置 visible 属性为 true 使组件变得可见。
    b.在组件上派发 creationComplete 事件。组件将因为布局的需要而被设置大小或被处理。该事件在组件创建的时候只会派发一次。
    c.在组件上派发 updateComplete 事件。

        大多数配置组件的工作发生在我们使用 addChild() 方法将组件添加到容器中的时候。这是因为在我们将组件添加到容器中之前,Flex 无法决定组件的大小,决定其继承来的样式属性,或者将其绘制在屏幕上。

我们也可以在 MXML 中定义我们的应用程序:

 

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">

    <mx:Box>

        <mx:Button label="Submit"/>

    </mx:Box>

</mx:Application>

        在 MXML 中创建组件时所产生的一系列步骤和上述在 ActionScript 中创建组件时产生的步骤是相同的。

        我们可以使用 removeChild() 方法将组件从容器中移除。如果不存在任何对组件的引用,组件最终将被 Flash Player 或者 AIR 的垃圾回收机制从内存中删除。

 

创建组件的步骤

        当我们实现可视组件的时候,我们重写组件的相关方法,定义新的属性,派发新的事件,或者执行应用程序所需的任何其他自定
义组件的操作。  

为了实现我们的组件,可以遵循下列步骤:

  1. 如果有必要,为组件创建任何所需的皮肤。
  2. 创建一个 ActionScript 文件。
    a.扩展一个基类,例如 UIComponent 类或者其他 UIComponent 的子类。
    b.指定用户可以通过 MXML 标签属性设置的属性。
    c.嵌入任何所需的图形及皮肤文件。
    d.实现构造函数。
    e.实现 UICommponent.createChildren() 方法。
    f.实现 UIComponent.commitProperties() 方法。
    g.实现 UIComponent.measure() 方法。
    h.实现 UIComponent.layoutChrome() 方法。
    i.实现 UIComponent.updateDisplayList() 方法。
    j.添加属性、方法、样式、事件和元数据。
  3. 将组件作为 ActionScript 文件或者 SWC 文件部署。

 

        我们不需要重写所有的组件方法来定义一个新的组件。我们只重写实现组件功能所需的方法。如果我们创建了一个继承自现有组件,例如 Button 控件或者 VBox 容器,的子类,为了实现新功能,我们必须实现那些必须重写的方法。

 

        例如,我们可以实现一个使用新机制来定义其默认大小的自定义 Button 控件。这种情况下,我们只需要重写 measure() 方法就可以了。

        或者我们可能实现一个 VBox 容器的子类。该类使用来自 VBox 的所有设置子对象大小的逻辑,但是容器将按从下到上的顺序来排列子内容,而不是从上至下。这种情况下,我们只需要重写 updateDisplayList() 方法就可以了。

 

可视组件需实现的接口

 

        Flex 通过接口将组件的基本功能分为可一点点实现的不相关联的元素。例如,为了让组件能够接收焦点,我们必须实现 IFocusable 接口;为了让组件能够参与布局过程,组件必须实现 ILayoutClient 接口。

        为了简化接口的使用,UIComponent 类实现了下表中除 IFocusManagerComponent 以及 IToolTipManagerClient 外的所有接口。然而,许多 UIComponent 类的子类实现了 IFocusManagerComponent 和 IToolTipManagerClient 接口。

        因此,如果我们创建了一个 UIComponent 的子类,或者创建了一个继承自 UIComponent 的类的子类,我们不需要实现这些接口。但是,如果我们创建了一个不是继承自 UIComponent 的组件,并且我们想在 Flex 中使用它,我们可能需要实现一个或多个下列接口。

注意: 对于 Flex,Adobe 推荐所有的组件都扩展自 UIComponent 类或者 UIComponent 的子类。
接口 用途
IChildList 表明容器中子内容的数量。
IDeferredInstantiationUIComponent 表明组件或者对象的实例化将被延迟。
IFlexDisplayObject 为皮肤元素指定接口。
IFocusManagerComponent 表明组件或对象是可接受焦点的,这意味着组件可以从 FocusManager 收到焦点。
UIComponent 类并没有实现该接口,因为它的一些扩展类不需要收到焦点。
IInvalidating 表明组件或者对象可以利用验证失效机制执行延迟的属性提交、测量大小以及绘图或者布局操作。
ILayoutManagerClient 表明组件或者对象可以参与到 LayoutManager 的一系列提交、测量和更新操作中去。
IPropertyChangeNotifier 表明组件支持特殊形式的事件传播机制。
IRepeaterClient 表明组件或对象可以与 Repeater 类一起使用。
IStyleClient 表明组件能够从其他对象继承样式,并且支持 setStyle() 和 getStyle() 方法。
IToolTipManagerClient 表明组件或由一个 toolTip 属性,因此它会被 ToolTipManager 所监控。
IUIComponent 定义我们必须实现一系列基本的 API,以使组件能够称为布局容器或列表的子元素。
IValidatorListener 表明组件可以监听验证事件,因此可以显示出一个验证状态,例如红色的边框及错误提示。

 

一个组合组件的具体实现

以下代码片段包含了讲解与注释

package com.custom.controls

{

    ////////////////////////////////////////

    //

    // 导入所有必须的类。

    //

    ////////////////////////////////////////

    import mx.core.UIComponent;

    import mx.controls.Button;

    import mx.controls.TextArea;

    import flash.events.Event;

    import flash.text.TextLineMetrics;

 

    ////////////////////////////////////////

    //

    // 当子 TextArea 组件的 text 发生改变的时

    // 候,ModalText 派发出一个 change 事件。

    //

    ////////////////////////////////////////

    [Event(name="change", type="flash.events.Event")]

 

    ////////////////////////////////////////

    //

    // 当设置 ModalText text 属性的值的时候,

    // 派发一个 textChanged 事件。

    //

    ////////////////////////////////////////

    [Event(name="textChanged", type="flash.events.Event")]

 

    ////////////////////////////////////////

    //

    // 当设置 ModalText textPlacement 属性

    // 的值的时候,派发一个 placementChanged

    // 事件。

    //

    ////////////////////////////////////////

    [Event(name="placementChanged", type="flash.events.Event")]

 

    /*** a) 扩展 UIComponent ***/

    public class ModalText extends UIComponent

    {

 

        /*** b) 实现构造函数。 ***/

        public function ModalText()

        {

            super();

        }

 

 

        /*** c) 为两个子组件定义变量。 ***/

        ////////////////////////////////////////

         //

        // 为子组件声明变量。

         //

         ////////////////////////////////////////

        private var text_mc:TextArea;

        private var mode_mc:Button;

 

 

        /*** d) 实现 createChildren() 方法。 ***/

        ////////////////////////////////////////

         //

        // 在创建子组件之前检查它们是否存在,这样在

         // 组件的子类里可以创建不同的子组件来代替它

         // 父类中定义的子组件。

         //

         ////////////////////////////////////////

        override protected function createChildren():void

        {

            super.createChildren();

 

            ////////////////////////////////////////

            //

            // 创建并初始化 TextArea 控件。

              //

            ////////////////////////////////////////              

            if (!text_mc)

            {

                text_mc = new TextArea();

                text_mc.explicitWidth = 80;

                text_mc.editable = false;

                text_mc.text= _text;

                text_mc.addEventListener("change", handleChangeEvent);

                addChild(text_mc);

            }

 

            ////////////////////////////////////////

            //

            // 创建并初始化 Button 控件。

              //

            ////////////////////////////////////////                   

            if (!mode_mc)

            {   mode_mc = new Button();

                mode_mc.label = "Toggle Editing Mode";

                mode_mc.addEventListener("click", handleClickEvent);

                addChild(mode_mc);

            }

        }

 

 

        /*** e) 实现 commitProperties() 方法。 ***/

        override protected function commitProperties():void

        {

            super.commitProperties();

 

            if (bTextChanged) {

                bTextChanged = false;

                text_mc.text = _text;

                invalidateDisplayList();

            }

        }      

 

 

        /*** f) 实现 measure() 方法。 ***/

         ////////////////////////////////////////

         //

        // 默认宽度等于文本的宽度加上按钮的宽度。

         // 高度由按钮指定。

         //

        ////////////////////////////////////////

        override protected function measure():void

        {

            super.measure();

 

             ////////////////////////////////////////////

             //

            // 既然 Button 控件使用了皮肤,我们就可以获取

              // 已计算好的 Button 控件的大小。

              //

            ////////////////////////////////////////////

            var buttonWidth:Number = mode_mc.getExplicitOrMeasuredWidth();

            var buttonHeight:Number = mode_mc.getExplicitOrMeasuredHeight();

 

            ////////////////////////////////////////////

             //

            // 默认的和最小的宽度等于 TextArea 控件的

              // measuredWidth 加上 Button 控件的

              // measuredWidth

              //

            ////////////////////////////////////////////

            measuredWidth = measuredMinWidth =

                text_mc.measuredWidth + buttonWidth;

 

            ////////////////////////////////////////////

             //

            // 默认的和最小的高度等于 TextArea 控件的高度

              // Button 控件的高度中较大的那个再加上文本

              // 周围的边框所产生的10像素。

              //

            ////////////////////////////////////////////

            measuredHeight = measuredMinHeight =

                Math.max(mode_mc.measuredHeight,buttonHeight) + 10;

        }

 

 

        /*** g)实现 updateDisplayList() 方法。 ***/

        ////////////////////////////////////////

        //

        // 根据 Button 控件的 label 文本及预留的10

        // 像素边框区域来设置 其大小。

         // 根据余下的组件区域设置 TextArea 的大小。

         // 根据 textPlacement 属性设置子组件的位置。

         ////////////////////////////////////////

        override protected function updateDisplayList(unscaledWidth:Number,unscaledHeight:Number):void

        {

            super.updateDisplayList(unscaledWidth, unscaledHeight);        

             ////////////////////////////////////////

            //

            // 左边和右边的边框各需要1像素,还需要在左边

              // 和右边各留3像素的空白。

              //

            ////////////////////////////////////////

            var usableWidth:Number = unscaledWidth - 8;

 

                          ////////////////////////////////////////

            //

            // 顶部和底部的边框各需要1像素,还需要在顶部

              // 和底部各留3像素的空白。

              //

            ////////////////////////////////////////

            var usableHeight:Number = unscaledHeight - 8;

            ////////////////////////////////////////

            //

            // 根据 Button 控件的文本计算其大小。

              //

            ////////////////////////////////////////

            var lineMetrics:TextLineMetrics = measureText(mode_mc.label);

            ////////////////////////////////////////

            //

            // 在文本周围添加10像素的边框区域。

              //

            ////////////////////////////////////////

            var buttonWidth:Number = lineMetrics.width + 10;

            var buttonHeight:Number = lineMetrics.height + 10;

            mode_mc.setActualSize(buttonWidth, buttonHeight);      

 

            ////////////////////////////////////////

            //

            // 计算文本的大小。在 Button TextArea

            // 之间设置5像素的间隔。

              //

            ////////////////////////////////////////

            var textWidth:Number = usableWidth - buttonWidth - 5;

            var textHeight:Number = usableHeight;

            text_mc.setActualSize(textWidth, textHeight);

 

            ////////////////////////////////////////

            //

            // 根据 textPlacement 属性设置控件的位置。

              //

            ////////////////////////////////////////

            if (textPlacement == "left")

            {

                text_mc.move(4, 4);

                mode_mc.move(4 + textWidth + 5, 4);

            }

            else

            {

                mode_mc.move(4, 4);

                text_mc.move(4 + buttonWidth + 5, 4);

            }          

 

            ////////////////////////////////////////

            //

            // 在子组件周围绘制简单的边框。

              //

            ////////////////////////////////////////

            graphics.lineStyle(1, 0x000000, 1.0);

            graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);        

        }

 

 

        /*** h) 添加方法、属性和元数据。 ***/

        ////////////////////////////////////////

         //

        // 一般情况下,为属性指定一个私有的持有变量。

         //

        ////////////////////////////////////////

        private var _textPlacement:String = "left";

 

        ////////////////////////////////////////

         //

        // textPlacement 属性创建一对 getter/setter

         //

        ////////////////////////////////////////

        public function set textPlacement(p:String):void

        {

            _textPlacement = p;

            invalidateDisplayList();

            dispatchEvent(new Event("placementChanged"));

        }

 

        ////////////////////////////////////////

         //

        // textPlacement 属性支持数据绑定。

         //

        ////////////////////////////////////////

        [Bindable(event="placementChanged")]

        public function get textPlacement():String

        {

            return _textPlacement;

        }

 

        private var _text:String = "ModalText";

        private var bTextChanged:Boolean = false;

 

        ////////////////////////////////////////

         //

        // text 属性创建一对 getter/setter

         //

        ////////////////////////////////////////

        public function set text(t:String):void

        {

            _text = t;

            bTextChanged = true;

            invalidateProperties();

            dispatchEvent(new Event("textChanged"));

        }

 

        [Bindable(event="textChanged")]

        public function get text():String

        {

                return text_mc.text;

        }

 

        ////////////////////////////////////////

                 //

        // 处理子组件派发的事件。

         //

        ////////////////////////////////////////

        private function handleChangeEvent(eventObj:Event):void

        {

               dispatchEvent(new Event("change"));

        }

 

         ////////////////////////////////////////

         //

        // 处理子组件派发的事件。

         //

        ////////////////////////////////////////

        private function handleClickEvent(eventObj:Event):void

        {

                text_mc.editable = !text_mc.editable;

        }

    }

}

<?xml version="1.0" encoding="utf-8"?>

<s:Application

         xmlns:fx="http://ns.adobe.com/mxml/2009"

         xmlns:s="library://ns.adobe.com/flex/spark"

         xmlns:mx="library://ns.adobe.com/flex/halo"

         xmlns:controls="com.custom.controls.*"

         width="600"

         height="480">

         <s:Panel title="自定义可视化组件演示"

                 width="80%"

                 height="80%"

                 horizontalCenter="0"

                 verticalCenter="0">

                 <controls:ModalText/>

         </s:Panel>      

</s:Application>

总结

        本节详细介绍了实现 ActionScript 可视组件的种种细节。一般情况下,我们通过继承 UIComponent 或者其子类来创建自定义的可视组件。我们需要重写特定的方法来实现我们所需的功能或逻辑。我们需要根据应用程序的需求及组件的初始化生命周期来确定组件最终该如何实现。此外我们还可以通过直接实现一些既定接口,而不是继承现有的类来创建能够在 Flex 中使用的可视组件。最后,通过创建 ModalText 组合组件,本节向大家展示了具体是如何实现自定义 ActionScript 可视组件的。

        本节大部分内容都翻译自 Adobe 官方文档 ActionScript Custom Components。因作者水平有限,因此有不当的地方,望大家能发邮件告知,以互相交流。此外,所有内容应以官方文档为准。

思考