在用 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);
}