The Event Flow

来自Blueidea
跳转至: 导航搜索


關於 ActionScript3 事件流。

事件流機制圖示

FlashPlatform EFA.jpg

 从概念上来讲事件流分成了三个部分:
 
 FlashPlatform Numbers1.jpg捕獲階段: 此階段包括從舞台到發送事件的目標對象的父節點間的所有節點。
 FlashPlatform Numbers2.jpg目標階段: 此階段只包括發送事件的目標對象節點。
 FlashPlatform Numbers3.jpg冒泡階段: 此階段包含的階段和捕獲階段相同,但此階段事件傳遞方向和捕獲階段剛好相反。
 
 一個事件必須經過EventDispatcher派發出來。
 根據DOM Tree定義。找到目標對象在DOM Tree中的定義並獲得此節點到DOM tree根節點的單根路徑。
 因此一個非顯示對象只能參與FlashPlatform Numbers2.jpg階段。因為一個沒有表象的非顯示對象在DOM中不存在父節點。
 而對於顯示對象通常都能有父顯示容器。因此在DOM Tree中通常可以找到父節點的定義。
 只有一種情況下例外,當一個顯示物件不包含任何子顯示元素同時沒有被加入到Display list(顯示列表)中。
 此是這個顯示對象和非顯示對象的一樣只能參與FlashPlatform Numbers2.jpg階段。


用這個圖所示結構來說明ActionScript3事件流機制

FlashPlatform EF.jpg

顯示列表(Display list)

實現圖示結構的代碼

這個例子用於創建如上圖中所示的結構。
我們為A,B,C1,C2分別加入了所有階段監聽。

package {
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  public class Test extends Sprite {
    //==========================================================================
    //  Constructor
    //==========================================================================
    /** Constructor */
    public function Test() {
      // init
      const A:Sprite = new Sprite();
      const B:Sprite = new Sprite();
      const C1:Sprite = new Sprite();
      const C2:Sprite = new Sprite();
      B.addChild(C1);
      B.addChild(C2);
      A.addChild(B);
      stage.addChild(A);
      // add event listeners
      A.addEventListener("myEvent", A_listener1, true);
      A.addEventListener("myEvent", A_listener2);
      B.addEventListener("myEvent", B_listener1, true);
      B.addEventListener("myEvent", B_listener2);
      C1.addEventListener("myEvent", C1_listener1, true);
      C1.addEventListener("myEvent", C1_listener2);
      C2.addEventListener("myEvent", C2_listener1, true);
      C2.addEventListener("myEvent", C2_listener2);
      // dispatch event
      // ${1}
    }
    //==========================================================================
    //  Event listeners
    //==========================================================================
    private function A_listener1(event:Event):void {
      trace("A處捕獲(非冒泡階段)");
    }
    private function A_listener2(event:Event):void {
      trace("A處捕獲");
    }
    private function B_listener1(event:Event):void {
      trace("B處捕獲(非冒泡階段)");
    }
    private function B_listener2(event:Event):void {
      trace("B處捕獲");
    }
    private function C1_listener1(event:Event):void {
      trace("C1處捕獲(非冒泡階段)");
    }
    private function C1_listener2(event:Event):void {
      trace("C1處捕獲");
    }
    private function C2_listener1(event:Event):void {
      trace("C2處捕獲(非冒泡階段)");
    }
    private function C2_listener2(event:Event):void {
      trace("C2處捕獲");
    }
  } // <- end class ->
}

基本的事件發送和捕獲

${1}處加入以下代碼

C1.dispatchEvent(new Event("myEvent"));

會看到以下輸出。

 A處捕獲(非冒泡階段)
 B處捕獲(非冒泡階段)
 C1處捕獲

這個圖可以解釋以上結果。

FlashPlatform EFTEST1.jpg

打開事件的冒泡會如何

${1}處代碼改為

C1.dispatchEvent(new Event("myEvent"));

會看到如下輸出。

 A處捕獲(非冒泡階段)
 B處捕獲(非冒泡階段)
 C1處捕獲
 B處捕獲
 A處捕獲

這個圖可以解釋以上結果。

FlashPlatform EFTEST2.jpg
 你可能會奇怪為甚麼C2_listener1函數並沒有執行。
 其實這個函數的存在是多餘的。因為這個監聽的useCapture為true
 IEventDispatcher.addEventListener方法的useCapture說明為
 判斷偵聽程式是否可在捕捉階段或目標與反昇階段運作。 如果 useCapture 設為 true,則偵聽程式只會在捕捉階段 (而不是在目標或反昇階段) 處理事件。
 如果 useCapture 為 false,則偵聽程式只會在目標或反昇階段處理事件。 若要在全部三個階段中偵聽事件,請呼叫 addEventListener 兩次,
 先將 useCapture 設為 true 後進行第一次呼叫,接著將 useCapture 設為 false 再進行第二次呼叫。
 我們可以圖示來簡單說明為甚麼這個監聽沒起任何作用。
FlashPlatform EFTEST3.jpg

如圖所示因為這個函數士這樣加入的。

C1.addEventListener("myEvent", C1_listener1, true);

因此它無法參與FlashPlatform Numbers2.jpg目標階段和FlashPlatform Numbers3.jpg冒泡階段。 然而這個監聽又是加在C1節點上。即FlashPlatform Numbers2.jpg目標節點物體。因此它起不到作用。

自定義事件

事件實質上是一種VO(Value Object) 所以推薦事件對象的屬性多使用只讀屬性。 創建起來很簡單。(注意常量定義單詞用_分開,賦值以小寫開頭每個單詞首字大寫,舞分隔符)

package events {
  import flash.events.Event;
 
  public class CustomEvent extends Event {
    // ${1}
    public static const MY_EVENT_TYPE:String = "myEventType";
    /** Constructor */
    public function CustomEvent(type:String, bubbles:Boolean = false,
                                cancelable:Boolean = false) {
      super(type, bubbles, cancelable);
      $data = {};
    }
    private var $data:Object;
    public function get data():Object {
      return $data;
    }
  } // <- end class ->
}

為了規範。也為了使用自定義事件可以更方便得知一個事件的作用。可以在${1}處加上註釋。

/**
 * 這個是字定義事件類型的描述。
 * <p>此事件具有以下屬性值:</p>
 * <table class="innertable">
 *    <tr><th>屬性名稱</th><th>值</th></tr>
 *    <tr><td><code>bubbles</code></td><td>false</td></tr>
 *    <tr><td><code>cancelable</code></td><td>false</td></tr>
 *    <tr><td><code>data</code></td><td>帶有用戶參數</td></tr>
 * </table>
 * @eventType myEventType
 */

這樣你可以得到一個很漂亮的asdoc檔

FlashPlatform EFTEST5.png

如果是使用的Flash Builder開發。那麼以下內容可以讓事件更容易使用。
你可以在將拋出此時間的特定類中聲名此事件。

package {
  import flash.events.EventDispatcher;
  /**
   * @eventType events.CustomEvent.MY_EVENT_TYPE
   */
  [Event(name="myEventType", type="events.CustomEvent")]
  public class MyEventDispatcher extends EventDispatcher {
    /** Constructor */
    public function MyEventDispatcher() {
    }
  } // <- end class ->
}

這樣在您調度此類的實例的addEventListener方法時,這個事件會自動列出。

FlashPlatform EFTEST4.png

如何創建事件的默認阻止行為

我們給上面的MyEventDispatcher類加上一個方法注意第3個參數必須是true

public function say():void {
  const SUCCESS:Boolean = dispatchEvent(new CustomEvent(CustomEvent.MY_EVENT_TYPE, false, true));
  if(!SUCCESS)
    return;
  trace("雷神也有春天");
}

這就是一個可阻止的事件派發機制。我們經常用到監聽TextEvent.TEXT_INPUT並在輸入一些我們不允許的字符時返回,也是使用這種方式。
接下來可以測試這段代碼是如何工作的。
我們在創建一個MyEventDispatcher對象並監聽自定義事件。

const MED:MyEventDispatcher = new MyEventDispatcher();
MED.addEventListener(CustomEvent.MY_EVENT_TYPE, eventHandler);
function eventHandler(event:Event):void {
  // ${2}
}
MED.say();

會得到以下輸出

 雷神也有春天

現在在${2}處加入代碼

event.preventDefault();
 於是就得不到任何的輸出了。

stopPropagation與stopImmediatePropagation的區別

package {
  import flash.display.Sprite;
  import flash.events.Event;
 
  public class Test extends Sprite {
    //==========================================================================
    //  Constructor
    //==========================================================================
    /** Constructor */
    public function Test() {
      const A:Sprite = new Sprite();
      const B:Sprite = new Sprite();
      A.addChild(B);
      addChild(A);
 
      A.addEventListener("myEvent", A_listener1, true);
      A.addEventListener("myEvent", A_listener2, true);
      B.addEventListener("myEvent", B_listener);
      B.dispatchEvent(new Event("myEvent"));
    }
    //==========================================================================
    //  Event listeners
    //==========================================================================
    private function A_listener1(event:Event):void {
      trace("A_listener1");
      // ${1}
    }
    private function A_listener2(event:Event):void {
      trace("A_listener2");
    }
    private function B_listener(event:Event):void {
      trace("B_listener");
    }
  } // <- end class ->
}

得到以下輸出

 A_listener1
 A_listener2
 B_listener

在${1}處加入代碼

event.stopPropagation();

得到以下輸出。後續節點的事件監聽被中斷了。

 A_listener1
 A_listener2

如果修改${1}代碼為

event.stopImmediatePropagation();

得到以下輸出

 A_listener1

那麼當前節點的優先級小於A_listener1的監聽函數也會被中斷。意義上的不許動,全部給我停下來。

參考資料