当前位置: 代码迷 >> Web前端 >> actionscript AS3的Event事件机制研讨
  详细解决方案

actionscript AS3的Event事件机制研讨

热度:576   发布时间:2012-11-07 09:56:10.0
actionscript AS3的Event事件机制探讨
超人来了,那就是ActionScript 3.0事件处理机制:

(1)统一。全部一色用addEventListener().

(2)所有的可视对象都可以接受和发送事件。
as3.0的类继承设计是深思熟虑的,所有的可视对象所属类都是DisplayObject的子类, DisplayObject又是EventDispatcher的子类。因此它们就都可以玩Event了,所以说,有个好的老子就是好啊。
而且有了崭新的事件冒泡机制,可以使事件层层上递到最上层的Stage,绝好的功能!
有了以上两点:黑羽的EventSender类也可以歇菜了。

(3)侦听器统一使用Function,不再使用Object了。 同时this关键字的记忆力“大大增强”,Delegate类也可以下岗了。

(摘要完毕|详细查看全文…)
 

回顾和比较

AS1.0玩家最爱用onClipEvent(), on(),又方便又直接。缺点在于逻辑分散到了各个舞台元件中,难以管理和维护。更加别说代码重用了。别跟俺说可以Copy, Paste,这不叫重用,这叫低级。

AS2.0中,增加了一些事件处理机制:

(1)回调函数: onLoad, onComplete等。相信兄弟们最熟悉的应该就是XML.onload回调函数了。

(2)事件侦听器型:
这个就是addListener(), addEventListener()这种类型的。发送事件有的是内置,有的通过dispatchEvent().
嗯,这种事件机制基本上就和AS3.0很像了。自从使用了ActionScript 2.0来开发项目,黑羽就尽可能的多用这种事件处理机制。这个习惯很好,基本上让黑羽对3.0的机制很快适应过来。

说道这里,黑羽要多扯几句2.0,抱怨一下它的不足,再让大家看看AS3.0的光明大道:

(1)2.0中除了UIComponent能自己发送事件,绝大部分类不能自己发送事件的,比如MovieClip, 或者一些自定义的类。黑羽还制作了一个EventSender的事件发送类来解决。
当然你可以通过扩展来解决MovieClip这些类来解决,但是在一些轻量级或者特殊运用中,还是用黑羽这个EventSender类更加方便。
比如说,你突然需要舞台上某个A_mc的运行到第20帧时发送一个”finished”事件出来,并且希望另外某个B_mc能够捕捉到这个事件,那么用一般的扩展方法不知道有多么麻烦!
(1、要重做一个带有事件发送功能的类和A_mc通过某种方式绑定。2、同时确保在B_mc中要能访问到发送事件的对象并addEventListenr)。
而用俺的EventSender类非常简单,A_mc中写 EventSender.send(“finished”, this), B_mc中写EventSender.addListener(“finished”, listenerFunc),并可以通过event.target属性直接定位A_mc,真是简单的不能再简单了。(请尽量以正规方法为主,不推荐频繁使用,不是好的编程习惯)

(2)2.0中,侦听器的记忆是”有毛病”的。如果是新手,会经常觉得侦听器函数的this关键字指向飘忽不定,常常弄错。而且看看高手的代码,一会儿是Object做侦听器,一会儿是function做侦听器,真是让人头痛。其实MacroMedia也很头痛,所以就出了Delegate这个官方类(补丁?)来解决这些问题。

(3)侦听器注册方式也有两种,一种是addListener(),如Key,一种是addEventListener(),如UIComponent类。为什么要这样搞两种?MacroMedia无辜的望着我,喃喃道偶也不完全清楚。

超人来了,那就是ActionScript 3.0事件处理机制:

(1)统一。全部一色用addEventListener().

(2)所有的可视对象都可以接受和发送事件。
AS3.0的类继承设计是深思熟虑的,所有的可视对象所属类都是DisplayObject的子类, DisplayObject又是EventDispatcher的子类。因此它们就都可以玩Event了,所以说,有个好的老子就是好啊。
而且有了崭新的事件冒泡机制,可以使事件层层上递到最上层的Stage,绝好的功能!
有了以上两点:黑羽的EventSender类也可以歇菜了。

(3)侦听器统一使用Function,不再使用Object了。 同时this关键字的记忆力“大大增强”,Delegate类也可以下岗了。

Event涉及到的内容极多,面很广。下面黑羽将从以下几个方面讲起:

一、如何接收事件?如何做到AS3.0的标准事件编程。

二、如何发送自定义事件?如何在OOP中正确使用AS3.0强大灵活的事件架构。
三种方式及其优劣,以及在何种情况下使用。

用继承EventDispatcher实现
用复合EventDispatcher实例来实现。
用接口IEventDispatcher实现
三、如何使用冒泡机制(即官方所称的Event Flow机制)?以及冒泡机制的原理。

四、Event的其他高级应用。

其实到目前为止,我的3.0开发学习笔记已经有3万字左右的东西了,但都是些纲要和代码,整理成文,尤其是用比较有条理和易懂的方式写出来会比较慢。况且开发中的代码是随手拈来,但写教程的代码往往要经过一些改写,使得重点更突出。所以如果更新有点慢,请耐心一些,呵呵。

如何接收事件?如何做到AS3.0的标准事件编程?

Event改变的部分很多,这两天有空时,黑羽就在想怎样用一个有条理的方式来讲解Event和它相关的诸多内容,让我们感到比较容易理解,记忆和接受。
我准备这样来讲解:

先给个接受事件的代码例子。在例子中,指出:

Event对象发生了什么变化
addEventListener语法的不同,原因,和const型必要性和用法
Listener和As2.0有何不同,和this关键字的“改进了的记忆力”
黑羽一贯的风格,先来一个例子。我很想给个短一点的代码例子,但是要达到清楚,全面和标准的示范,我还是决定采用这个Document Class的示例。我会在每个代码段注一些注释,大家不明白的地方回贴说一下,我会尽量解答。

Document Class的含义和相关用法并不难,忘了的兄弟看我第3篇教程:
AS3.0教程(3):Document Class特色为我们带来了什么?

好,继续….

新建一个as文件,拷贝以下代码,命名为AddListener.as。

新建一个fla,命名为 “黑羽黑羽我爱你.fla”(本教程推荐使用,倘若不遵从可能导致喝凉水塞牙泡MM被踢炒股被套等严重后果,霍哈哈。Just kidding。)。设置它的文档类(Document Class)为AddListener.

package {
      import Flash.display.Sprite;
      import Flash.events.MouseEvent;   
   
     //哈哈,看到了没,Document Class不仅可以扩展MovieClip,也可以扩展Sprite
     //package里面的类名要和文件名相同
      public class AddListener extends Sprite {
          public function AddListener() {

              //用package外面定义的类KingdaSprite创建一个实例,由于同文件中,所以不用import啦
              var outsideChild:KingdaSprite = new KingdaSprite(0×00FF00, “outside_sprite”);
          addChild(outsideChild);//没有了这一句,你啥都看不到。
  
              outsideChild.addEventListener(MouseEvent.CLICK, inclassHandler);//注册类里面的侦听器
              outsideChild.addEventListener(MouseEvent.CLICK, outsideHandler);//注册类外面的侦听器                      
          }

          private function inclassHandler(event:MouseEvent):void {
              trace(“类里面的侦听器侦听到MouseEvent事件: ” + event);
       trace(“this关键字指向:”+this);
          }
       
      }
}

function outsideHandler(event:MouseEvent):void {
        trace(“类外面的侦听器侦听到MouseEvent事件: ” + event);
        trace(“this关键字指向:”+this);
}

import flash.display.Sprite;
import flash.events.MouseEvent;

//这个类就是画一个矩形,然后你点击这个矩形会发出标准鼠标click的事件
class KingdaSprite extends Sprite {
    public var nickname:String;

public var ColorNum:uint;

      //colorNumber就是#ffcc00这种类型的数,在AS3中推荐用新的uint型来标记它
       public function KingdaSprite(colorNumber:uint, nameString:String) {
           ColorNum = colorNumber;
           nickname = nameString;                 
           graphics.beginFill(ColorNum);
           graphics.drawRect(0,0,100,100);
           graphics.endFill();
       }
}



鼠标一点击创建出来的绿色矩形,会看到输出

类里面的侦听器侦听到MouseEvent事件: [MouseEvent type="click" bubbles=true cancelable=false eventPhase=2 localX=64 localY=80 stageX=64 stageY=80 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0]

this关键字指向:[object AddListener]

演示完毕,我们来讲第一个话题

(一)Event对象发生了什么变化

AS2.0中创建event 对象是很随意的,只要这个对象有一个String属性叫做type的就可以了,甚至连target都可以省掉。不要以为这不规范不应该做,看看Flash类源码,Macromedia的程序员可不只一次的这样使用过。所谓上梁不正下梁歪,就是这样。(其实严格说也没什么不对,有时候是不需要target。但没有标准就是不好)
然后你在Event Object中爱添加什么就添加什么。极其自由,也就等于极其混乱。

3.0不同了,所有的事件必须都要继承自Event这个类(全饰名称flash.events.Event)。不然,哼哼,事件发送是不能成功地。

说说Event这个类,AS3.0的基石类是Object,Event是直接继承自Object的,开国大佬级别的。它里面定义了一些基本的事件名称,比如ACTIVATE(FlashPlayer得到系统焦点时事件),ADDED(对象被添加到显示时发送的事件)。AS3.0中有个好功能是cancel事件,但Event中这些基本事件统统是不能被cancel的,听起来似乎很NB。这不细说了,Event太多内容了。以后写高级内容时在提到如何运用吧。要想Flash玩的转,Event类必须很精通。正所谓江湖人称:”平生不识Event,就称闪客也枉然。”

这样有什么好处?

规范了事件的定义
大量由此衍生的类省去了我们大量的时间和重复开发的成本。
规范事件的定义放到下一部分讲。至于内置的Event子类好处,大家请看上面fla的运行输出,MouseEvent就是内建的一个Event子类,它的好处一看它的内容就明白,从AS3.0起,任何一次点击事件我们都可以得到:

事件发生时鼠标的MC相对坐标(localX=64 localY=80)和绝对坐标(stageX=64 stageY=80)。
ctrl键,shift键,alt键有没有按下。你试试按着ctrl键再点击一下方块,那么ctrlKey就为true了。(其余参数不解释,涉及到高级运用。部分会在Event最后一节提到该怎么运用)
二)addEventListener语法的不同,原因,和const型必要性和用法
本例如果是AS2.0,那么代码是这样写的: 本例是3.0,如果你trace一下其中的MouseEvent.CLICK,输出的也是字符串”click”。outsideChild.addEventListener(“click”, inclassHandler)
看起来AddEventListener的方法和以前也没什么大的出入。和这世界上大多数相同的事物一下,表象的类似却掩盖着本质的巨大差异。我们先从最小的差异讲起:1.使用类静态属性,用const定义事件字符串名称变量AS3.0中用了一个新的关键字定义了事件名称字符串变量,代码是
public static const CLICK:String = “click”

const,是英文constant的缩写,意思使不变的,常量。那就意思很明白了,一旦这种类型的常量被定义就不可再更改。好处通俗的说就是规范好项目,规范你自己,也规范任何其他项目人员,动不了这个变量。
你可能还是会不屑一顾,这么小的一个改动,对我没什么用!
可我的AS2.0开发血泪经验是,我曾经花了一个下午来找bug,最后却发现是某个类的addEventlistener()里面的事件名称”click”手误打成了”cilck”。编译器可不管这个。
如果是3.0,你打成了MouseEvent.CILCK,那么编译时立刻会报错提醒你,多好啊。一个项目一个人做十几个类还好办,自己认真点还能顶的住;如果是一个项目几个人几十个类,那么没有const和static的帮忙,管理事件类型还真是有点麻烦。即使实现了也没有AS3.0这么简单直观。
回到代码,我们要记住,日后我们开发自己的Event类时,也要像这样,用public static const来定义我们自己的事件名称。
如何自定义自己的事件,我会在EventDispatch那一节讲述。

看看代码,我们还发现AS3.0中侦听器也发生变化了,只能用function来做侦听器,不再用Object。

2.addEventListener高级运用

AS3.0中对侦听器的改进远远不止以上这些,看一看addEventListener的实现接口:

有三个莫名奇妙的参数。可是当你知道这三个参数背后隐藏的巨大改进之后,相信你要大叫三个哇塞。

第一个参数,目前暂不解释,埋个伏笔,留到EventDispatcher那一节,讲actionscript3崭新的Event Flow事件流机制.

第二个参数:优先级。 有趣吧,在ActionScript 3.0中我们可以控制事件的优先级,从而达到控制function侦听器的执行顺序。如果你不填这个参数,那么事件默认为同一个级别0,事件的执行按先来后到的天经地义的顺序。
如果设为1,那么事件级别降一个档次,稍后执行。数字越高优先级越高。级别可以为负数。
(注意: Flex Builder 2 Beta3中事件级别是越低越高)

第三个参数,讲的是是否设为弱引用。兄弟们初学As2.0时一定经常忘了在删除Listener对象时,却忘了removeEventListener吧。这会导致很多莫名奇妙的情况发生。也是最常见的诡异bug种类之一。(这种情况往往不被发现,在后台默默的消耗大量资源。)现在可以用弱引用可以在某种程度上解决这种bug:
设为true,就是告诉垃圾回收器,这个侦听器function的引用是弱引用。一旦这个侦听器在运行时只剩下了这一个弱引用,那么垃圾回收器可以不理它,直接把它回收了,节省资源。
从AS3.0引入这个弱引用这个概念就可以看出,AS3.0是如何的重视资源管理和有效率的运用。一个标准的重量级的程序语言必须具有这样的特征。
现在AS3.0有了!

function addEventListener(eventName:String,
listener:Object,
useCapture:Boolean=false,
priority:Integer=0,
useWeakReference:Boolean=false):Boolean;

在讲listener 和 this关键字之前,我们先来讲讲一个高级话题:弱引用的使用原则。新手可以不看,因为暂时用不到。但这个话题很有必要。当设计大型RIA应用程序时,弱引用必须要了解。
弱引用从原则上来讲,其引入是为了防止无意识的对象保留(unintentional object retention)引起的内存泄漏。
什么是无意识的对象保留?一般情况下,对象的逻辑生命周期和实际生命周期应当相同。但是在某些情况下,比如事件侦听器机制,我们可能会创建一个引用,它在内存中生存的时间比我们预期的要长很多。比如说,下面的例子中,即使删掉了侦听器lis,但事件还是能被继续捕捉。这是由于并没有removeEventListenr,那么系统中还会保留着对侦听器的引用。
如果没有把addEventListener中对侦听器的持有改成弱引用,那么这个侦听器会一直存在、耗掉内存不被人发觉。但如果设成了弱引用,那么垃圾回收器过段时间后会将侦听器占用的内存回收,此时侦听器从物理意义上才真正的被销毁了。
在销毁之前,这个侦听器还会继续存在,继续作用。这就是为什么下例中虽然已经把useWeakReference设为了true,但是还是会有作用。
那么什么时侯垃圾回收器(Garbage Collector)才会来回收呢?
唔,这要看它高兴了,快的话10分钟,慢的话一个月。哈哈,开玩笑,垃圾回收器的工作时间咋一看确实是不可测的,但也有规律可循。但这个问题探讨在本系列教程中显得过于艰深,黑羽会单独撰文来说明垃圾回收器的部分工作机制。那么推论来了,我们凭什么要把useWeakReference设为false呢?除非我们有意要让某个侦听器在失去了其他所有引用的时候还要工作,才有可能这样做。但谁会有这样疯狂的想法呢?道哥还是包世宏?
所以,我建议,大家不妨养成习惯,把useWeakReference设为true。
当然最好的做法是始终记得不用侦听器了,一定要removeEventListener。在这一点上,我们要坚持“始乱终弃”!Listener和As2.0有何不同,和this关键字的“改进了的记忆力”
DOM Level 3中规定的是用Object来做listener。该Object有用来处理事件的方法(method)。
AS3虽然是按照DOM Level 3事件机制设计的,但也不完全听话,有自己独立的想法。在AS3中,侦听器就是函数。也只能用函数来侦听。
其实在ActionScript3中,Function实质也是一种特殊的Object,和AS2.0有相似之处。
见我的文章:ActionScript高级技巧:深入了解Function
因此AS3.0为什么限定用Function,这和它的架构有关,不细说了。
但有趣的是,在AS3.0中,对IEventDispatcher的定义中,仍然按照DOM3标准用Object来做Listener。见黑羽上一篇教程和随后的评论。
那么问题来了,function中this关键字的指向会怎样?

//这个是Document Class,建个fla,绑定这个class。忘了,就看我第三篇教程。
package
{
import flash.display.MovieClip;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;

public class LearnEvents extends MovieClip
{
    function LearnEvents() {
     var lis:Function = function () {
      trace (“听到了鼠标Click事件!”);
     }
     var kingda_s:KingdaSprite = new KingdaSprite(0xFFCC00, “kingdaSquare”);
     addChild(kingda_s);
     kingda_s.addEventListener(MouseEvent.CLICK, lis, false, 0, true); //瞧,最后一个参数,已经把弱引用设为true了
     lis = null;    //这一句,我已经从逻辑上删掉lis了,大家作证啊
     trace (“侦听器还在吗?:”+ lis);    //lis也确实指向null了。但你只要继续点击方块,你会发现Click事件仍然被捕捉到。
 
    } 
}
}
import flash.display.Sprite;
import flash.events.MouseEvent;
//这个类就是画一个矩形,
class KingdaSprite extends Sprite {
public var nickname:String;
public var ColorNum:uint;

//colorNumber就是#ffcc00这种类型的数,在AS3中推荐用新的uint型来标记它
public function KingdaSprite(colorNumber:uint, nameString:String) {
    ColorNum = colorNumber;
    nickname = nameString;
    graphics.beginFill(ColorNum);
    graphics.drawRect(0,0,100,100);
    graphics.endFill(); 
}

}

AS2.0开发者对AS2.0 事件机制中 this关键字的水性杨花应该深有认识。如果用function做侦听器,那么谁发出事件,this就指向谁。这就等于对象之间乱搞关系,啊呀呀。所以MM派了一个Delegate代理警察类来管理。唉,糜乱的岁月不堪回首啊。函数在哪个对象里(应该叫method),那么this就指向谁。不在对象里,那么就指向global。

//http://www.kingda.org
package {
      import flash.display.Sprite;
      import flash.events.MouseEvent;   

      public class AddListener extends Sprite {
          public function AddListener() {

              //用package外面定义的类KingdaSprite创建一个实例,由于同文件中,所以不用import啦
              var outsideChild:KingdaSprite = new KingdaSprite(0×00FF00, “outside_sprite”);
          addChild(outsideChild);//没有了这一句,你啥都看不到。
  
              outsideChild.addEventListener(MouseEvent.CLICK, inclassHandler);//注册类里面的侦听器
              outsideChild.addEventListener(MouseEvent.CLICK, outsideHandler);//注册类外面的侦听器                      
          }

          private function inclassHandler(event:MouseEvent):void {
              trace(“类里面的侦听器侦听到MouseEvent事件: ” + event);
       trace(“this关键字指向:”+this);
          }
       
      }
}

function outsideHandler(event:MouseEvent):void {
        trace(“类外面的侦听器侦听到MouseEvent事件: ” + event);
        trace(“this关键字指向:”+this);
}

import flash.display.Sprite;
import flash.events.MouseEvent;

//这个类就是画一个矩形,然后你点击这个矩形会发出标准鼠标click的事件
class KingdaSprite extends Sprite {
    public var nickname:String;
    public var ColorNum:uint;

      //colorNumber就是#ffcc00这种类型的数,在AS3中推荐用新的uint型来标记它
       public function KingdaSprite(colorNumber:uint, nameString:String) {
           ColorNum = colorNumber;
           nickname = nameString;                 
           graphics.beginFill(ColorNum);

//ActionScript 2.0例子:拷贝以下代码到第一帧,拖一个button到舞台,命名为kingda_btn;
function lisFunc() {
trace (“亲爱的,你会指向谁:”+ this.name);     //我们本意应当是指向_root;
}

kingda_btn.addEventListener(“click”, lisFunc); //点一下button,看看指向谁。

          graphics.drawRect(0,0,100,100);
           graphics.endFill();
       }
}

  相关解决方案