Flex的“事件之旅”
Flex的“事件之旅”
信号灯的应用中,为什么在点击按钮后,就会自动调用 myEventHandler方法呢?看官答道:“我们把myEventHandler作为事件侦听器注册到了容器ctnButtons上了!”没错,但 是为什么在容器上注册了侦听器,Flash Player就能够调用myEventHandler方法呢?Flash Player如何发现有这样一个侦听器?如果我们在其他的容器,比如ctnButtons的父容器traficLight上为同样的事件注册了侦听器,会 发生什么?Flash Player维护了一个侦听器队列吗?如果是这样的话,那队列中成员的顺序又如何呢?
其实并没有一个所谓的侦听器队列。但Flash Player确实维护了一个树状列表,即显示列表(详见第7章的7.1.1节“显示列表”(见第126页))。Flash Player创建事件对象,并调度该对象到事件流。事件流就是事件对象在显示列表中的“旅程”。
当事件发生时(比如用户按下了按钮),Flash Player即创建Event对象,事件之旅也由此刻开始了。然而,事件之旅的起源地并不在“此地”,而是从显示列表的根节点 Stage(flash.display.Stage,一个特别的显示对象容器,显示列表的根节点)开始,然后沿着列表逐级向下,直到发生事件的对象。之 后,又按相反的方向逐级向上回到根节点。没错,事件之旅是个往返旅程。而且,需要强调的是,事件旅程并不包括从根节点Stage对象到发生事件的对象之间 的所有节点,而只涉及发生事件的对象本身和它的父容器。比如,信号灯应用中,Flash Player就不会检查lblLightInfo对象,因为它并不是三个按钮对象的父容器。在这段旅程中,Flash Player逐一检查这些对象是否针对发生的事件注册了侦听器,为事件对象赋值,并调用侦听器。
概念上,Flex的事件旅程分为三个阶段:捕获阶段、目标阶段和冒 泡阶段,如图6-2所示。既然事件之旅同显示列表密切相关,在旅程开始之前,我列出了信号灯应用的树状显示列表以供参考。同时标出了当用户按下“红色”按 钮时事件旅程的三个阶段。
图6-2 信号灯应用的显示列表和事件流
6.2.1 target和currentTarget
如我们刚才提到的,在事件被触发后,Flash Player就会创建事件对象,并逐一检查“事件旅程”上的节点是否针对发生的事件注册了侦听器,为事件对象赋值,并调用侦听器。事件对象源自 flash.events.Event类。事件对象currentTarget属性的值会在事件流中改变,而target属性则不会变化。(关于事件对 象,我们将在6.3节(见第106页)深入讨论)由此,开发者能够通过currentTarget属性获知事件旅程现在停在了哪个节点上。
· currentTarget属性:事件旅程中,currentTarget属性代表了Flash Player正在检查的节点对象。比如,当Flash Player遍历到ctnButtons对象,那么event.currentTarget就是ctnButtons对象。
· target属性:target属性就是发生事件的对象。在信号灯应用中,event.target就是用户所点击的按钮对象,在事件旅程中,该属性的值 始终不变。信号灯应用中,侦听器myEventHandler利用event.target来获取被点击按钮上的标签数据。此时赋给 event.currentTarget属性的则是HBox容器对象ctnButtons。
6.2.2 捕获阶段
在事件发生之 后,Flash Player将沿着显示列表,从根节点Stage开始遍历事件发生对象的所有父对象,这个阶段称之为“捕获阶段”。捕获阶段截止到发生事件对象的直接父对 象。在遍历的过程中,Flash Player会检查是否有节点注册了事件侦听器,并对生成的事件对象赋值,调用事件侦听器方法。
在信号灯应用中,在事件发 生后,Flash Player将顺序检测Stage、SystemManager、Application、traficLight、ctnButtons,至此为捕获阶 段。
需要指出的是,在默认情况 下,容器们并不在捕获阶段“侦听”事件。信号灯的应用中,侦听器myEventHandler实际上是在冒泡阶段被调用的。当我们在ctnButtons 上注册侦听器时,默认设置了addEventListener方法的use_capture属性为“false”。如果你希望在捕获阶段调用侦听器,那么 要使用如下代码注册侦听器,见代码6-6:
代码6-6:在捕获阶段响应按钮事件
ctnButtons.addEventListener(MouseEvent.CLICK,myEventHandler,true);
通常情况下,开发者更习惯于 利用事件旅程的目标阶段和冒泡阶段处理事件响应。
6.2.3 目标阶段
目标阶段只涉及一个对象, 即发生事件的对象。在目标阶段,事件对象的target和currentTarget对象被赋予相同的值,也就是发生事件的对象。
在信号灯应用中,如果用户 按下了红色按钮,那么在目标阶段,Flash Player将只检测btnRed对象。如果我们在btnRed按钮上注册侦听器方法(假设为redEventHandler(event Event)),则在目标阶段,系统会调用redEventHandler方法。在该方法中,如果检测event.target和 event.currentTarget属性,将会发现两者均指向btnRed按钮。
6.2.4 冒泡阶段
冒泡阶段与捕获阶段涉及的节点完全相同,但遍历顺序却恰好相反。在 Flash Player对发生事件对象检查完毕后,将从其直接父容器开始,沿着显示列表向上,最终返回到Stage根节点。
在信号灯的应用中,目标阶段结束后,Flash Player将依次检测ctnButtons、traficLight、Application、SystemManager,最终返回到Stage,此 为冒泡阶段,而事件之旅也告一段落。
6.2.5 信号灯应用的事件之旅
最后,通过代码再一次体验Flex的“事件之旅”。依然以信号灯的 应用为基础,经过改造的应用代码见代码6-7,新的项目名为EventJourney。
代码6-7: EventJourney项目代码
<mx:Script>
<![CDATA[
import mx.controls.Alert;
private function initApp():void {
btnRed.addEventListener(MouseEvent.CLICK,myEventHandler);
traficLight.addEventListener(MouseEvent.CLICK,myEventHandler);
ctnButtons.addEventListener(MouseEvent.CLICK,myEventHandler);
lblLightInfo.addEventListener(MouseEvent.CLICK,myEventHandler);
this.addEventListener(MouseEvent.CLICK,myEventHandler);
//在捕获阶段调用EventListener
btnRed.addEventListener(MouseEvent.CLICK,myEventHandler,true);
traficLight.addEventListener(MouseEvent.CLICK,myEventHandler,true);
ctnButtons.addEventListener(MouseEvent.CLICK,myEventHandler,true);
lblLightInfo.addEventListener(MouseEvent.CLICK,myEventHandler,true);
this.addEventListener(MouseEvent.CLICK,myEventHandler,true);
}
private function myEventHandler(event:Event):void {
trace(event.currentTarget.toString() + " 在事件阶段" + event.eventPhase);
}
]]>
</mx:Script>
在initApp()方法中,我们为btnRed、 traficLight、ctnButtons、lblLightInfo和Application本身(通过this)分两次注册了同一个侦听器 myEventHandler。第二次注册中,我们设置use_capture参数为true。
在myEventHandler中使用trace方法输出了侦听器 被调用时的当前节点对象和事件所在阶段。事件属性event.eventPhase标志着当前所处事件旅程的阶段。返回值为unit类型,包含了代表着三 个阶段的numeric值。分别为:
· 捕获阶段:EventPhase.CAPTURING_PHASE=1;
· 目标阶段:EventPhase.AT_TARGET=2;
· 冒泡阶段:EventPhase.BUBBLING_PHASE=3。
在按下红色按钮后,输出结果如代码6-8所示。
代码6-8: EventJourney项目代码输出结果
EventJourney0 在事件阶段1
EventJourney0.traficLight 在事件阶段1
EventJourney0.traficLight.ctnButtons 在事件阶段1
EventJourney0.traficLight.ctnButtons.btnRed 在事件阶段2
EventJourney0.traficLight.ctnButtons 在事件阶段3
EventJourney0.traficLight 在事件阶段3
EventJourney0 在事件阶段3
关于结果的说明:
· 尽管我们为btnRed按钮注册了两次侦听器,但是由于Flash Player只在目标阶段检查该按钮,因此侦听器只在目标阶段被调用一次;
· 尽管我们为lblLightInfo标签也注册了侦听器,但由于该对象并不在事件旅程覆盖的节点中,因此不会产生任何输出。