AS3事件之旅

(一)什么是事件?
    让我们来举几个例子让大家明白其中的道理

    举例一--天地会会员注册量超过20000   
    事件类型:天地会新闻
    事件类型再细分: 有关注册量的新闻
    事件时间:2008年11月27日
    事件敏感数字: 20000

    举例二---鼠标滚轮向上滑动

    事件类型:MouseEvent
    事件类型再细分:滚轮动作
    事件信息:向上滚动(dlta>0)

    举例三---用户按下字母键F

    事件类型:keyBoardEvent
    事件类型再细分:键盘被按下
    事件信息   :keyCode=45

    由上面三个例子可以看出,事件没什么好神奇的, 事件不过就是新闻或者消息罢了
    新闻有政治的,有八卦的,有科技的,也有经济的。所以事件也可以分类,有鼠标事件、键盘事件、加载完成等等事件。
    新闻(消息)有发生时间,发生地点,相关人物,所以AS3的事件里面也包含各种信息。鼠标事件里面可以有鼠标点了哪一个Sprite,鼠标点击处的坐标等信息、而键盘事件里面则可以有键盘是被按下还是被弹起,按下键的ASCII值,以及Ctrl是否被按下等信息。
  1. 经典语录:一切都是对象(All is object)
复制代码
一个数组是一个数组对象,一个影片剪辑是一个影片剪辑对象,甚至还可以说一个int值也是一个int对象,所以把AS3里面的事件叫做事件对象就不足为奇了。 大家不要把事件对象理解成抛出事件的对象或者事件流经的对象,"事件对象"是一个消息。事件对象里面包含事件的相关信息。为了容易理解,在这里我们暂时把事件对象比喻成一个信封(事件对象),信封里面装着信(事件对象的具体信息)。
===============================================================================================================================
    (二)什么是目标对象(target),当前目标对象(currentTarget)?

    首先要知道 所有的事件都是由FlashPlayer的事件管理中心发出的,当鼠标点击一个Sprite时,FlashPlayer会向这个Sprite发送一个鼠标事件对象(MouseEvent)从而形成一个事件流,而同在舞台的另一个Sprite则不会收到FlashPlayer发出的事件,因为事件流是流向前一个Sprite的不会经过第二个Sprite。
   
    熟悉AS2的人知道,在AS2时代很多在内层的MovieClip都被剥夺了响应鼠标的权力。AS3为我们提供了强大的DOM事件流机制,使得所以对象都有平等的权力接收到相关事件信息。
   
    事件流的三个阶段:
  1.     1 捕获阶段 (EventPhase.CAPTURING_PHASE)包括从舞台到目标节点范围内的所有节点
  2.     2 目标阶段 (EventPhase.AT_TARGET)权包括目标节点
  3.     3 冒泡阶段 (EventPhase.BUBBLING_PHASE)从目标节点的父节点返回到舞台的行程中遇到的节点
复制代码
不是所有的事件都有这三个阶段。如Timer、URLLoader,它们的事件对象将直接派送给目标对象(target).它们只包含目标阶段而没有捕获阶段和冒泡阶段。它们不会像显示对象容器(DisplayObjectContainer)那样有可能被一个DisplayObjectContainer对象包含或者自己包含一个DisplayObjectContainer对象,它们往往是单独存在的。

    对于可显示对象我们应建立显示对象列表的概念,一个即使对象有大小有颜色,如果不被addChild到舞台(或者已经在舞台上的显示对象)里是不会显示的,这样FlashPlayer在渲染时候只需要渲染显示对象列表中的对象即可,可以大大节约资源。

    当一个显示对象不在显示列表中时,FlashPlaye会把事件直接派送给它,这个时候就没有事件流,也没有捕获阶段和冒泡阶段,only目标阶段。
    例如:
  1. var i=0;
  2. var s = new Sprite();
  3. s.addEventListener(Event.ENTER_FRAME,onEnter);
  4. function onEnter(evt:Event) {
  5.         i++;
  6.         s.name=i;
  7.         trace(s.name);
  8. }
复制代码
对象不在显示列表中也可以接收事件对象(消息)。
   
    但是当多个显示对象重叠的时候,经典的事件流出现了。(附图一)
    首先用代码生成附图一
  1. //outer
  2. var outer = new Sprite();
  3. outer.name="Outer";
  4. outer.graphics.beginFill(0xFF0000);
  5. outer.graphics.drawRect(0,0,100,100);
  6. //mid
  7. var mid = new Sprite();
  8. mid.name="mid"
  9. mid.graphics.beginFill(0x00FF00);
  10. mid.graphics.drawRect(0,0,50,50);
  11. //inner
  12. var inner = new Sprite();
  13. inner.name="inner";
  14. inner.graphics.beginFill(0x0000FF);
  15. inner.graphics.drawRect(0,0,25,25);
  16. //addChild
  17. outer.addChild(mid);
  18. mid.addChild(inner);
  19. addChild(outer);
  20. outer.x=outer.y=100;
  21. //添加侦听器
  22. outer.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
  23. inner.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
  24. mid.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
  25. function mouseDownHandler(evt:MouseEvent){
  26.         trace("事件流当前阶段:"+evt.eventPhase)
  27.         trace("鼠标点击处最内层的显示对象(target)是:" + evt.target.name)
  28.         trace("事件当前流经显示对象(currentTarget)是:"+evt.currentTarget.name)
  29.         trace("===============================================================")
  30.    
  31.         }
复制代码
鼠标点击蓝色部分时的输出
  1. 事件流当前阶段:2
  2. 鼠标点击处最内层的显示对象(target)是:inner
  3. 事件当前流经显示对象(currentTarget)是:inner
  4. ===============================================================
  5. 事件流当前阶段:3
  6. 鼠标点击处最内层的显示对象(target)是:inner
  7. 事件当前流经显示对象(currentTarget)是:mid
  8. ===============================================================
  9. 事件流当前阶段:3
  10. 鼠标点击处最内层的显示对象(target)是:inner
  11. 事件当前流经显示对象(currentTarget)是:Outer
  12. ===============================================================
复制代码
由附图一我们可以看到三个不同的Sprite---outer(红色),mid(绿色),inner(蓝色)。outer里面包含mid,mid里面包含inner

    当我们用鼠标点击蓝色部分时,鼠标点击处最内层的对象是inner,如前面所说,AS3中要保证所有对象都有平等接收事件消息的权力。捕获阶段便是为解决这一问题而设的,

    1.首先PlashPlayer发出一个鼠标事件对象(一个信封),把它送到显示对象列表(outer,mid,inner都在显示列表中)。
    2.捕获阶段的任务是找到鼠标点击处最内层的对象,也就是目标对象(target),所以事件对象(FlashPlayer寄出来的那封信)沿着显示对象列表一直往下找,先找到outer再找到mid,再找到inner,在这个找的过程中,事件在流动,所以形成了事件流。
    3.终于我们找到了目标对象(target)inner,目标阶段也就到了,FlashPlayer终于保证了最基层群众也有知情权。
    4.现在基层群众接收到了事件,它看完之后,事件对象(那封信)开始往回上传了,传说中的冒泡阶段开始了,outer,mid又一次接收到了同一个事件。

    所以目标对象(target)一般来说都是最里层的对象也就是FlashPlayer要找的基层群众,在上面的例子中target始终是是inner
    当前对象(currentTarget)则是注册侦听器的对象,一般是容器也是事件流当前流经的注册了侦听器的对象,在上面的例子中currentTarget先后是inner,mid,outer

    正确地区分target和currentTarget非常重要。如果我们要用Sprite做一个按钮,按钮里面在放一个TextField用来显示按钮名称(附图二),代码如下:
  1. var button=new Sprite();
  2. button.graphics.beginFill(0xFF0000);
  3. button.graphics.drawRect(0,0,50,25);
  4. var t=new TextField();
  5. t.text="START";
  6. button.addChild(t);
  7. addChild(button);
  8. button.x=button.y=100;
  9. //button.mouseChildren=false
  10. addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
  11. function mouseDownHandler(evt:MouseEvent) {
  12.         evt.target.visible=false;
  13. }
复制代码
我们的本意是在按下START按钮后让其消失,但是如果我们的鼠标点到了TextField上时,发现消失的是TextField而不是button。因为鼠标点击处最内层的对象也就目标对象target是t(TextField对象)而不是button(Sprite对象),很多时候新手会被这种情况搞得一头雾水,如何解决这一问题?只要把button的mouseChildren属性设为false即可让其子对象不参与响应鼠标事件,这样便可以解决target和currentTarget混淆的问题。如果子对象不需要参与鼠标事件响应,完全可以把容器的mouseChildren属性设为false,还可以节约资源!
 
附图一.jpg
 

附图二.jpg

  (三)如何处理事件对象?
    由上面的例子还可以看出,在显示对象列表中的每一个对象都会有两次机会收到同一个事件对象(那封信)。只不过我们在注册侦听器时使用addEventListener(MouseEvent.MOUSE_DOWN,listener)时默认不在捕获阶段响应(useCapture=false);
    在这两次收到事件的时候,注册了事件类型的对象的侦听器会被触发。

    注册侦听器的方法:

  1. 对象.addEventListener(事件类型,侦听器)
复制代码

其中侦听器可以是函数或者类的方法(类的方法其实也是函数)。
    在侦听器中,我们需要作出相应的动作,下面一个Mp3播放器为例(例图三)
    假设播放器中有一个进度条ProgressBar,首先为ProgressBar注册一个侦听器

  1. ProgressBar.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
复制代码

当用户点击进度条时触发了一个MouseEvent.MOUSE_DOWN事件,那么按前面所讲的ProgressBar会收到这个事件。ProgressBar接收到这个事件后onMouseDown函数会被调用,onMouseDown函数要做两件事
    1.改变ProgressBar蓝色部分的长度来更新进度。
    2.改变当前Mp3的播放位置。
通学的伪码写法是:

  1.     function onMouseDown(evt:MouseEvent){
  2.         ProgressBar.progress=.....;//伪码
  3.         mp3.play(ProgressBar.progress);//伪码
  4.     }
复制代码

上面的方法看似没有问题,但是OOP的思想告诉我们,Mp3的播放位置的控制应该由Mp3控制核心来控制,而不是由ProgressBar来控制。所以可以改进上述代码

  1.     function onMouseDown(evt:MouseEvent){
  2.         ProgressBar.progress=.....;//伪码
  3.         dispatchEvent(new ProgressChangeEvent(ProgressBar.progress))//发送一个进度事件,告诉控制器,进度改变了
  4.     }
复制代码

进度条只改变自己的进度,并告诉Mp3播放控制器,我的进度变了,你也该换位置放了哟~~~
怎么了?没见过dispatchEvent和ProgressChangeEvent吧~~,下面讲解自定义事件
==========================================================================================================================================
    (四)自定义事件和自定义事件类
    正确区分自定义事件和自定义事件类,如果你只是简单地发送一个你自己的事件而不需要更多信息,可以使用

  1. var myInfo:Event=new Event("myOwnEvent");
  2. dispatchEvent(myInfo)
复制代码

来实现,我们看到自定义事件很简单,只需给Event类传入一个特殊的不与内置事件类型重复的字符串即可
    可以做如下测试:

  1. //outer
  2. var outer = new Sprite();
  3. outer.name="Outer";
  4. outer.graphics.beginFill(0xFF0000);
  5. outer.graphics.drawRect(0,0,100,100);
  6. //mid
  7. var mid = new Sprite();
  8. mid.name="mid"
  9. mid.graphics.beginFill(0x00FF00);
  10. mid.graphics.drawRect(0,0,50,50);
  11. //inner
  12. var inner = new Sprite();
  13. inner.name="inner";
  14. inner.graphics.beginFill(0x0000FF);
  15. inner.graphics.drawRect(0,0,25,25);
  16. //addChild
  17. outer.addChild(mid);
  18. mid.addChild(inner);
  19. addChild(outer);
  20. outer.x=outer.y=100;
  21. //添加侦听器
  22. outer.addEventListener("myOwnEvent",myOwnEventHandler,true);//注意此处将useCapture设为true所以会在捕获阶段调用侦听器
  23. mid.addEventListener("myOwnEvent",myOwnEventHandler,true);//同上
  24. inner.addEventListener("myOwnEvent",myOwnEventHandler);
  25. function myOwnEventHandler(evt){
  26.         trace("事件流当前阶段:"+evt.eventPhase)
  27.         trace("鼠标点击处最内层的显示对象(target)是:" + evt.target.name)
  28.         trace("事件当前流经显示对象(currentTarget)是:"+evt.currentTarget.name)
  29.         trace("===============================================================")
  30.    
  31.         }
  32. var myInfo=new Event("myOwnEvent")
  33. inner.dispatchEvent(myInfo)//由inner向FlashPlayer提出请求把一个myOwnEvent的事件对象加入事件流
复制代码

输出:

  1. 事件流当前阶段:1
  2. 鼠标点击处最内层的显示对象(target)是:inner
  3. 事件当前流经显示对象(currentTarget)是:Outer
  4. ===============================================================
  5. 事件流当前阶段:1
  6. 鼠标点击处最内层的显示对象(target)是:inner
  7. 事件当前流经显示对象(currentTarget)是:mid
  8. ===============================================================
  9. 事件流当前阶段:2
  10. 鼠标点击处最内层的显示对象(target)是:inner
  11. 事件当前流经显示对象(currentTarget)是:inner
  12. ===============================================================
复制代码

可以看到我们自定义的事件被加入到了事件流中并且被outer,mid,inner收到了
这里要补充一点,dispatchEvnet的调用对象将会被作为事件的目标对象(target)
晕了没?
你可以修改一下上面的代码来帮助你理解

  1. var myInfo=new Event("myOwnEvent")
  2. trace(myInfo.target)//输出调用dispatchEvent方法前的target的名字,输出结果是null
  3. inner.dispatchEvent(myInfo)
  4. trace(myInfo.target.name)//输出调用dispatchEvent方法前的target的名字,输出结果是inner
复制代码

如果你认真看完了上面的文字,你应该对自定义事件有了一定了解,在一般情况下,这种方法已经够用了,如果你还需要更复杂的事件的话,你就可以考虑创建你自己的事件类

2008-12-4 15:29:23 上传

下载次数: 10

附图三.jpg

(五)编写自己的事件类
   在上面的Mp3播放器例子中我们提到了给Mp3播放控制部分发送一个ProgressChangeEvent事件,现在继续
   给它发送这个事件的目标是要让它知道播放进度改变了,还要让它知道进度变成了多少,这就是参数progressBar.progress的作用
自定义事件类

  1. package
  2. {
  3.         import flash.events.Event;

  4.         /**
  5.         * ...
  6.         * @author Flameshark
  7.         * Date:   2008.12.4
  8.         */
  9.         public class ProgressChangeEvent extends Event
  10.         {
  11.                 static private const PROGRESS_CHANGE = "progresschange"; //是不是跟MouseEvent.MOUSE_DOWN长得很像,其实MouseEvent差不多也是这么写出来的
  12.                 public var position:Number = 0;                          //这是我们新加的变量,用来存储进度信息。
  13.                 public function ProgressChangeEvent(pos:Numbre)
  14.                 {
  15.                         super(PROGRESS_CHANGE);       //调用父类的构造函数,将PROGRESS_CHANGE设置为默认事件类型
  16.                         position = pos;
  17.                 }
  18.                 public override function clone():Event //你应负责任地重写clone方法
  19.                 {
  20.                         var cloneEvent = new ProgressEvent();
  21.                         cloneEvent.position = this.position;
  22.                         return cloneEvent;
  23.                 }
  24.                 public override function toString():String//你应负责任地重写toStrin方法
  25.                 {
  26.                         return formatToString("ProgressChangeEvent", "type", "bubbles", "cancelable", "eventPhase", "position");
  27.                 }
  28.                
  29.         }
  30.        
  31. }
复制代码

一个修秀的程序员应该在自定义类中重写clone和toString方法,不论你是否会用到这两个方法!!因为在调用dispatchEvent(myInfo)时实际是把myInfo对象的一个拷贝给了dispatchEvent方法。
-_-!!终于写完了,现在我们可以使用自己写的事件类了。
把上面的代码保存成ProgressChangeEvent.as文件,然后在同一目录下新建一个fla文件F9打开动作面板输入:

  1. import ProgressChangeEvent//其实可以省略,因为在同一目录下

  2. //PlayerContralCenter
  3. var PlayerContralCenter=new Sprite();
  4. PlayerContralCenter.name="PlayerContralCenter"
  5. PlayerContralCenter.graphics.beginFill(0x00FF00);
  6. PlayerContralCenter.graphics.drawRect(0,0,100,100);
  7. //progressBar
  8. var progressBar=new Sprite();
  9. progressBar.name="progressBar"
  10. progressBar.graphics.beginFill(0xFF0000);
  11. progressBar.graphics.drawRect(0,0,100,5);
  12. //addChild
  13. PlayerContralCenter.addChild(progressBar);
  14. addChild(PlayerContralCenter);
  15. progressBar.y=50
  16. PlayerContralCenter.x=PlayerContralCenter.y=100

  17. //PlayerContralCenter侦听ProgressChangeEvent事件,将useCapture设置为true的原因是我们的自定义事件类是不冒泡的(你也可以修改成可冒泡的),所以必须在捕获阶段响应
  18. PlayerContralCenter.addEventListener(ProgressChangeEvent.PROGRESS_CHANGE,handler,true);
  19. progressBar.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);


  20. function handler(evt:ProgressChangeEvent){
  21.     trace("我是"+evt.currentTarget.name+",我收到了"+evt.type+"事件,"+"事件信息是"+evt.position);
  22. }
  23. function mouseDownHandler(evt:MouseEvent){
  24.         var pos=evt.target.mouseX/evt.target.width;
  25.     progressBar.dispatchEvent(new ProgressChangeEvent(pos));
  26. }
复制代码

输出:

  1. 我是PlayerContralCenter,我收到了progresschange事件,事件信息是0.42
  2. 我是PlayerContralCenter,我收到了progresschange事件,事件信息是0.84
  3. 我是PlayerContralCenter,我收到了progresschange事件,事件信息是0.94
  4. 我是PlayerContralCenter,我收到了progresschange事件,事件信息是0.97
  5. 我是PlayerContralCenter,我收到了progresschange事件,事件信息是0.05
  6. .............
复制代码

大家可以在附件下载代码

终于~~终于,我们的事件之旅结束了。
昨天看了ActionScript权威指南,才知道自己对事件机制的理解是多么肤浅,在此写出自己的心得,希望能对大家有所帮助~~
事件机制永远不是个简单的东西,还有很多就不在多说了(三楼都被我用完了)..
大牛轻拍~

【完结】2008.12.4 17:38

=================================================================

一般来说,只有位于显示列表中的显示对象调度的某些事件才会有这3个阶段(另一些事件虽然是显示列表中的显示对象触发的,但也只有目标阶段,如EnterFrame事件)。其他对象调度的事件直接发送到事件目标对象。
位于显示列表中的显示对象调度事件时,可以这样认为,Flash Player在内存中自己创建了一个事件对象,然后把该事件对象发送到显示列表的顶级(Stage对象),事件对象在显示列表中向触发事件的事件目标对象流动,直到到达显示对象目标之前,就是捕获阶段,事件对象流动到事件目标,是目标阶段,然后,该事件对象会离开事件目标向显示列表的顶级(Stage对象)回流,最后又回到Stage对象,这就是冒泡阶段。事件流动过程中,如果显示列表中某一节点定义了该事件的事件处理函数,该事件对象就会作为事件处理函数的参数传递,事件处理函数就会被执行。
没有位于显示列表中的对象触发事件时,事件对象会直接流动到事件目标对象,只有目标阶段。

再举个例子,舞台上有个名为button的影片剪辑元件,button里面嵌套了一个名为status的动态文本对象(可在Flash中,双击该button元件,再用文本工具添加文本,并在属性窗口中命名)。你可以在button元件上addEventListener(MouseEvent.CLICK),监听鼠标点击事件。
当用鼠标点击到button元件中的文本位置时,会触发一个MouseEvent.CLICK事件,这个事件的目标对象是status文本对象。可以认为Flash Player在内存中创建了一个MouseEvent对象,这个对象有着自己的很多属性和一些方法。事件对象向着显示列表中的status事件目标流动,沿着Stage——button——status的方向流动(捕获阶段),到达status后(目标阶段),然后离开status向Stage方向回流(冒泡阶段),再次经过button时,由于button注册并定义了事件处理函数,该事件对象传递给事件处理函数,事件处理函数得到执行。
你没看错,用addEventListener注册事件侦听器时,默认情况下,事件被触发时,如果注册事件处理侦听器的对象不是事件目标,事件是在冒泡阶段-事件对象回流时才被事件处理函数处理的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值