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.......