天天看點

AS3事件機制概述

事件機制是AS3的核心功能之一,沒有充分掌握事件機制的方方面面,就不能算是精通AS3語言。

1. AS3事件機制的主要成員

IEventDispatcher:事件派發對象接口,定義了添加、派發、移除、是否監聽指定事件、是否觸發指定事件接口

EventDispatcher:事件派發對象接口的實作者,使用者無法撇開EventDispatcher而自行實作IEventDispatcher接口,無法直接繼承EventDispatcher時,必須把EventDispatcher作為執行個體變量。

Event:事件基類,所有事件類均基于此類實作

2. 觀察者模式

AS3事件機制實作的是觀察者模式。

IEventDispatcher充當了Subject角色,EventDispatcher相當于ConcreteSubject對象,Event相當于Observer,ConcreteSbserver相當于Event的子類。Attach相當于addEventListener,Detach相當于removeEventListener,Notify相當于dispatchEvent。Event沒有Update。

3,顯示清單中事件流的三個階段

在一個事件的整個生命周期内,共分為三個階段:

在捕獲階段與冒泡階段均可能經過N個節點,在目标階段僅可能有一個節點。

使用stopPropagation可阻止對事件流中目前節點的後續節點中的所有事件偵聽器進行處理。使用stopImmediatePropagation可阻止對事件流中目前節點中和所有後續節點中的事件偵聽器進行處理。

顯示清單中事件流三階段與事件機制本身沒有直接關系。在AS3顯示清單中,為什麼要有事件流,為什麼不是直接到達目标對象?

4,IEventDispatcher接口講解

AS3事件機制的精髓基本全在這個接口中。

addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void

使用 EventDispatcher對象注冊事件偵聽器對象,以使偵聽器能夠接收事件通知。

最常用的是前面二個參數。第三個參數辨別該監聽器是否會在捕獲階段被觸發。第四個參數priority辨別該同類事情監聽器被調用的優化級。第五個參數辨別該監聽器是否易于被回收,預設為false,并且永遠應該預設為false,如果監聽器可以被回收,應該手動處理,而不是交給Flash Player。

對于監聽同一類事件的監聽器,priority高者優化被調用。在FP及Flex SDK中,priority最大不會超過200,是以,如果要設定top level的事件監聽,此值應該設定在200以上。

在大型應用中,最好把各個層所要用到的priority分一下組,例如200-220分派給Core Level。

removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void

從 EventDispatcher對象中删除偵聽器。

如果在addEventListener時,useCapture為true,此時在removeEventListener時,useCapture參數應與之相同。EventDispatcher内部維護了兩個listener集合,一個盛裝useCapture為false的listener,另一個盛裝為true的。

5,Event類執行個體化講解

Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false)

建立一個作為參數傳遞給事件偵聽器的 Event對象。

Event 類的方法可以在事件偵聽器函數中使用以影響事件對象的行為。某些事件有關聯的預設行為。例如,doubleClick事件有關聯的預設行為,此行為突出顯示事件發生時滑鼠指針下的詞。通過調用 preventDefault()方法,您的事件偵聽器可以取消此行為。通過調用 stopPropogation()或 stopImmediatePropogation()方法,還可以使目前事件偵聽器成為要處理事件的最後一個事件偵聽器。

cancelable辨別該事件是否可阻止與取消。一般FP定義的内部事件類型均不可以取消,如CANCEL,CLOSE,OPEN,ADDED等,一般IMG事件均可以取消,如CLOSING,EXITING等,凡是可以取消的事件,均有一個關聯的可以取消的行為。開發者在自定義事件也應遵守這一規則。

開發者應當保證type在應用 程式 中是唯一的,bubbles用于辨別事件在到達目标對象後是否仍向下傳遞。

Event對象在事情流結束之後,如果沒有其它引用,即可被GC回收。目前Event需要二次派發時,使用clone方法複制事件。

6,MouseEvent事件

滑鼠事件是FP内InactiveObject對象内在支援的事件,這個事情由InactiveObject執行個體化、派發,并且總是bubble等于true的。對于不需要滑鼠事件的對象,應當把mouseChildren與mouseEnabled設為flase,以優化程式性能。

7,Flash Player内部對事件的強力支援

FP本身是多線程的,隻不過目前未對開發者開放API。在FP内部,有一個線程專門用于處理事件,事件的處理總是在桢周期的前期進行,并且不會受到其它線程的影響。

FP是異步的,Event的派發與listener的執行并不是緊密銜接的,當你派發一個事件之後,不能指望監聽這個Event的Listener馬上執行。

PureMVC放棄AS3内部支援的事情機制不用,自己用觀察者模式實作了一套Command體系,是對FP中獨立事件線程的非合理浪費。

8,事件與代的概念

在FP内部,從宏觀上講,總是派發一拔事件,處理一拔代碼,然後再派發一拔事件,再處理一拔代碼,如此反複,看起來事件具有代(generation)的概念。代與事件流有關,也與桢周期内的執行模式有關。

9,為什麼要有事件流三階段?

當使用者在FP中單擊時,宿主環境僅能告訴FP使用者進行了單擊行為以及單擊的坐标,卻并不能告訴FP到底單擊了哪一個對象,哪一個MC,這是不可能的,因為是什麼對象、有什麼對象,隻有FP自已知道。

在FP中,共有兩種渲染模式,一種為保留模式,另一種為立即模式,無論是哪一種渲染模式,FP交給浏覽器或作業系統的最終渲染内容總是一張張圖檔,FP像幻燈片放映一樣向使用者展示互動與動畫。是以,當使用者單擊時,永遠隻是單擊點,看得見的點,FP拿到這些點之後,在内部的顯示清單結構上周遊,首先從上向下走,隻要目前顯示對象囊括了單擊點,并且是透明的(下面還有顯示對象),便一直往下走,直到目标對象,然後再原路一路向上走,這便是事件機制的三階段。

由于顯示對象可以是透明的,FP并不知道開發者設想的使用者真正想單擊的是不是目标對象,有可能是捕獲階段的對象,同樣的對象也有可能想在冒泡階段處理,為了提供更大的靈活性,FP在顯示清單中實作了事件流的三步機制,數以千萬開發者的實踐證明它是非常有階值的。

單擊之外的其它滑鼠事情與之類似。

10,優化程式性能的第一準則

及時移除不再需要的事情監聽,是保證垃圾回收、優化程式性能的最淺顯、最容易、最為開發者所忽視的行碼準則之一。以下代碼是通用的,在函數内部移除事件監聽的方法:

e.currentTarget.removeEventListener(e.type, arguments.callee);

11,停止冒泡事情的派發

除了移除不必要的事情監聽,停止冒泡事情的繼續派發也是提高程式運作效率的常用方法之一。該方法多用于MouseEvent事情,代碼為:

e.stopPropagation();

or

e.stopImmediatePropagation();

但是事情冒泡有時卻是十分有用的,在某處阻止了事件冒泡,有可能是以另一處的監聽無法觸發,這種bug十分隐蔽。

AS3的事件流就三個階段,捕獲 >目标 > 冒泡

而在滑鼠事件中,共有10種滑鼠事件,分别如下:

點選事件: MouseEvent.CLICK ,MouseEvent.DOUBLE_CLICK

按鍵事件: MouseEvent.MOUSE_DOWN ,MouseEvent.MOUSE_UP

懸停事件: MouseEvent.MOUSE_OVER ,MouseEvent.MOUSE_OUT ,MouseEvent.ROLL_OVER ,MouseEvent.ROLL_OUT

移動事件: MouseEvent.MOUSE_MOVE

滾輪事件: MouseEvent.MOUSE_WHEEL

最令我不解的就是

懸停事件: MOUSE_OVER  ,MOUSE_OUT  ,ROLL_OVER  ,ROLL_OUT

它們的具體工用是相似的

MOUSE_OVER = ROLL_OVER 

MOUSE_OUT = ROLL_OUT

唯一不同的是前者參與事件流的冒泡階段,而後者則不參加,

黑羽書上的例子類似,一個内部有文本框的按鈕,

MOUSE_OVER  ,MOUSE_OUT 事件中,滑鼠移到按鈕上,會觸發over事件,當滑鼠繼續移,移到按鈕裡的文本上時,

就會觸發文本的MOUSE_OVER事件,同一時間,按鈕的MOUSE_OUT事件也會觸發

而如果使用ROLL_OVER ,ROLL_OUT呢,這種情況下,滑鼠移入按鈕後,隻要不移出按鈕範圍,按鈕的OUT事件是不會觸發的.

(________________________________

//括号内的内容為後期補充,實然是最終結果,建議先跳過看後面的,回頭再看本段文字

此處我掉入了一個誤區,不明白為什麼移上子mc會觸發移出事件,然後又觸發移入事件,

其實不然,是事件流的原因,因為事件流機制預設是在冒泡階段偵聽的

是以整個流程解析就是,

1.先是滑鼠移入按鈕範圍,觸發按鈕mc的①MOUSE_OVER事件,向上冒泡,沒有其它對象偵聽了,

2.滑鼠繼續移,移入内部影片剪輯a的範圍時,觸發mc的②MOUSE_OUT事件,同時又觸發a的③MOUSE_OVER事件,

3.進入子影片剪輯a的冒泡階段,因為a的父對象mc有偵聽④MOUSE_OVER事件的,是以會觸發mc的移入事件

4.滑鼠移出a影片剪輯範圍(仍未移出mc範圍)時,觸發a偵聽的⑤MOUSE_OUT事件,

5.進入子影片剪輯a的冒泡階段,觸發mc的移出事件⑥MOUSE_OUT

6.滑鼠重新移入到mc影片剪輯的範圍,觸發mc的⑦MOUSE_OVER事件

7.滑鼠移出mc範圍,觸發mc的⑧MOUSE_OUT事件

//滑鼠移入mc範圍,未移入子影片剪輯a範圍

//外部_移入_目前 mc _目标 mc mc的MOUSE_OVER生效①

//滑鼠移入子影片剪輯a範圍

//外部_移出_目前 mc _目标 mc mc的MOUSE_OUT生效②

//内部_移入_目前 a _目标 a   mc.a的MOUSE_OVER生效③

//外部_移入_目前 mc _目标 a  冒泡階段,mc的MOUSE_OVER生效④

//滑鼠移出子影片剪輯a範圍

//内部_移出_目前 a _目标 a   mc.a的MOUSE_OUT生效⑤

//外部_移出_目前 mc _目标 a  冒泡階段,mc的MOUSE_OUT生效⑥

//外部_移入_目前 mc _目标 mc mc的MOUSE_OVER生效⑦

//滑鼠移出影片剪輯mc範圍,回到舞台

//外部_移出_目前 mc _目标 mc mc的MOUE_OUT生效⑧

上面是trace出來的結果,下面是源代碼,

場景中一個大影片剪輯mc,套一個小影片剪輯a

  複制内容到剪貼闆

  代碼:

  mc.a.addEventListener(MouseEvent.MOUSE_OVER,onFunA)

mc.a.addEventListener(MouseEvent.MOUSE_OUT,onFunB)

mc.addEventListener(MouseEvent.MOUSE_OVER,onFunC)

mc.addEventListener(MouseEvent.MOUSE_OUT,onFunD)

function onFunA(_evt:MouseEvent){

trace("内部_移入_目前www.shengshiyouxi.com",_evt.currentTarget.name,"_目标",_evt.target.name)

}

function onFunB(_evt:MouseEvent){

trace("内部_移出_目前",_evt.currentTarget.name,"_目标",_evt.target.name)

}

function onFunC(_evt:MouseEvent){

trace("外部_移入_目前",_evt.currentTarget.name,"_目标",_evt.target.name)

}

function onFunD(_evt:MouseEvent){

trace("外部_移出_目前",_evt.currentTarget.name,"_目标",_evt.target.name)

}

因為displayObject中 DisplayObjectContainer(容器)對象有一個屬性mouseChildren,控制子顯示對象是否接受事件,一般看到上面的話,而又知道mouseChildren屬性的朋友,就會有疑問了(包括剛才的我),那這樣我設顯示對象的mouseChildren屬性為false不就可以避免MOUSE_OUT事件在按鈕内部觸發了嗎?

對了,這點黑羽在最後說明白了,在某些情況下,我們需要在子顯示對象上寫事件的,如果設了mouseChildren為false,則很不友善了.

最後,我對事件的冒泡過程不太明确,自己寫了個小測試來驗證了.

在場景中再一個矩形影片剪輯,執行個體名mc

輕按兩下進入後,再畫一個矩形(稍小的),做成影片剪輯,執行個體名a

這樣做成了一個父子套的關系,

然後寫代碼

  複制内容到剪貼闆

  代碼:

  mc.a.addEventListener(MouseEvent.CLICK,onFunA)

mc.addEventListener(MouseEvent.CLICK,onFunB)

function onFunA(_evt:MouseEvent){

trace(_evt.currentTarget.name,"_",_evt.target.name)

}

function onFunB(_evt:MouseEvent){

trace(_evt.currentTarget.name,"_",_evt.target.name)

}

  運作,在單純mc的範圍上單擊,傳回的target是mc,currentTarget也是mc

輸出結果:

mc _ mc

而如果滑鼠再移入一點,在a影片剪輯上單擊的時候,

事件流先到達目标階段,觸發a的偵聽,然後冒泡階段,再到mc

是以target一直是a,但currentTarget目标會在兩次觸發中分别為a(内層)和mc(外層)

輸出結果:

a _ a

mc _ a

上面這個實驗就是想證明自己的一個想法,冒泡事件是否觸發的順序是從底到頂的,上面的輸出證明了這點了解.

這裡可以傳回上面看括号裡的内容了,看完再接下面這段總結

總結:

1.事件流是面向DisplayObject的一個過程機制,但凡顯示對象觸發的事件,必有這個流過程,自上而下,再自下而上

2.事件流機制是在同一條路徑上的父子關系的顯示對象都會參與的(預設)

3.參與事件流的對象,對内部子對象,同樣會觸發MOUSE_OUT事件的

4.最重要的一點就是,子對象觸發的事件,隻要父對象有偵聽,那麼無論如何,父對象都會觸發一次所偵聽的事件

而且順序是子對象先觸發事件,然後父對象再觸發(這是由冒泡階段的順序觸發的)

5.将addEventListener函數中的第三個參數設為true,則隻在捕獲階段偵聽,對于沒有子對象的元素,事件是不會觸發的,隻有當子對象同樣偵聽相同僚件時,才會觸發事件(因為沒有目标階段)

反正要了解事件機制,三個階段的執行順序及執行因素了解好後,下面的原理就很好了解了

如果設定addEventListener函數的第三個參數為true,會中斷目标階段的檢測,但它始終會參加事件流,

是以ROLL_OVER和ROLL_OUT是實作不參加事件流(捕獲和冒泡均不參加)操作的方法

呵呵,又加深了一點了解,開心ing.......

繼續閱讀