这是转自9RIA的一片文章,觉得很适合初识FLASH AS3.0的同学们,所以转过来给cnblogs的同学们做参考:
深入理解ActionScript 3.0的事件框架
在这篇教程里,我将会解释关于ActionScript 3.0事件框架的一些东西。看完这篇教程,你就会对“事件”,“事件触发者”和“事件监听器”如何工作有一个清晰的认识。
你将会学到什么?
因为AS3的事件框架很庞大,我们将会分别学习组成事件框架的所有部分。包括以下几方面:
1,事件
2,事件触发者
3,事件监听器
4,事件流
5,常用事件
6,注意事项和技巧
第1步:事件框架的简化描述
我深知如果直接开始讲专业术语,你会很难理解事件框架,因此我首先描述一个现实生活中的例子。
整个情形是事件框架的一个比喻,具体情形如下:
我是“计算机艺术”杂志的忠实读者,我每天都等着看最新一期的“计算机艺术”。当我收到新期刊后,开始阅读它。
第2步:分析事件框架的这个比喻
这里一共发生了这么几件事:
1,我收到新的期刊,收到期刊就是一个“事件”。
2, 这个事件发生在某处,发生在我身上,我就是“事件的触发者”。
3,我等待新期刊的到来,我是一个对象,我等待这个事件发生。我就是监听这个事件发生的对象,我是具有“事件监听器”的对象。这个事件监听器监听一个特定的事件(收到新的期刊)。
4,当我收到期刊,我开始阅读。当这个事件发生,我做了一些事,执行了一个方法。我就是处理了这个事件。我这时候做的事情就叫做“事件处理器”或者“监听方法”。
第3步:技术术语——事件
我刚才提到的一些东西:
1, 事件
2,事件触发者
3,事件监听器
4,事件处理器/监听方法
“事件”就是一个描述发生了什么的对象,在第1步中对应的是新期刊的到来。大多数情况下,你会看到一个事件被写成类似的结构:
MouseEvent.CLICK
这段代码包含了两部分:
1, MouseEvent 这是一个包含一个或多个事件的类。多采用驼峰式的书写方法。
2,CLICK 这是一个事件类型。多采用纯大写字母。
事件类型其实是一个string类型的静态常量。这听起来很奇怪,但是一个事件其实就是一个字符串。运行如下代码段:
trace(MouseEvent.MOUSE_MOVE);
你会得到这样的输出“mouseMove”。我们刚才trace了MouseEvent类中的静态变量MOUSE_MOVE。事件就是一个字符串!但是这个字符串代表发生了一件事,例如在这里表示鼠标的移动。
第4步:技术术语——事件触发者
一个事件总是在某处发生的。事件发生的地方就是事件触发的地方。触发事件的根源在EventDiapatcher这个类中。一定要认识到,事件在哪儿发生的就是在哪儿触发的。因此,如果movie clip A触发了一个事件,添加到movie clip B上的事件监听器(将在第5步介绍)是无法接收到这个事件的。
简而言之,所有的显示对象都有内置的dispatchEvent(event:Event)方法。
var myMC:MovieClip = new MovieClip();
myMC.dispatchEvent(new Event(MouseEvent.CLICK));
大多数情况下,你不需要手动地触发事件,事件都是自动触发。例如,如果我点击movie clip A,就是自动触发事件MouseEvent.CLICK。
第5步:技术术语——事件监听器
当一个特定事件发生的时候,我们作为Flash开发者,希望做些事情来响应这个事件。事件监听器正是我们需要用的。事件监听器有点特殊,没有自己单独的类。事件监听器是添加到一个对象上的。事件监听器是“监听”一个特定事件的一些对象。当这个事件触发时,会调用一个方法,也就是处理器。
下图显示了addEventListener()方法的语法,用来添加一个事件监听器:
实际上,这些并不是addEventListener方法接收的所有参数,还有三个参数我没有写出来。你基本上用不到它们,尤其是当你刚开始使用时间监听器。让我们看看addEventListener()方法的完整结构。
addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
下面讨论一下其余的3个参数:
1, useCapture:当设置这个参数为true时,我们讲在捕获阶段监听这个事件。默认情况下,这个参数为false,这样我们会在目标阶段和冒泡阶段监听这个事件。我们将在第10步讨论事件流。
2, priority:虽然看起来所有的事件监听器是同时工作的,其实并非如此。有些情况下,监听器之间会相互冲突。这个参数是为了确保一个特定的事件监听器具有比其它事件监听器更高的优先级。
3,useWeakReference:当这个参数设置为true时,就是在监听对象和监听器之间创建一个弱连接。什么意思呢?正常情况下,一个对象如果没有引用指向它就会被垃圾回收器回收。当删除一个添加了事件监听器的对象后,如果还有其它引用指向事件监听器,它就还会存在。这在“强内存引用”的情况下发生。使用“弱内存引用”,当添加事件监听器的对象被删除后,事件监听器也会被删除。我个人喜欢将这个参数设置为true(因此我使用的是“弱内存引用”)。
删除一个事件监听器和添加一个同样简单。
//this adds an event listener to the stage
stage.addEventListener(MouseEvent.CLICK, clickHandler);
//this removes the event listener added to the stage
stage.removeEventListener(MouseEvent.CLICK, clickHandler);
第6步:事件处理器/监听方法
当一个事件监听器监听的事件发生时,会调用一个方法。这个方法就是“事件处理器”或叫做“监听方法”。下图展示了处理器的语法:
注意处理器中的参数,这个参数是必不可少的。监听方法只有一个参数,并且不运行有多个。事件本身会包含自身的信息,这个会在第9步中介绍。
第7步:添加事件监听器和处理器
现在,我想要myMC来响应触发的事件,因此我添加一个事件和一个处理器方法。代码如下:
//create our myMC movie clip
var myMC:MovieClip = new MovieClip();
//let myMC dispatch the event MouseEvent.CLICK
myMC.dispatchEvent(new Event(MouseEvent.CLICK));
//add an event listener to myMC, that listens to the event MouseEvent.CLICK, and will call clickHandler
myMC.addEventListener(MouseEvent.CLICK, clickHandler);
//define the handler function
function clickHandler (event:Event){
trace("I heard the event MouseEvent.CLICK");
}
然后测试你的影片(Windows:Ctrl+Enter,Mac:cmd+Enter,这里用的是Flash Authoring Tools开发环境)。
你是否看到输出了?没有?呵呵,我也没有。我们会在下一步看看到底怎么回事。
第8步:代码顺序
那么,到底怎么回事呢?这不可能是语法错误,至少我没有看到任何报错信息。的确,这不是技术上的错误。再看一遍代码,这次要谨记,代码是一行一行执行的:
//create our myMC movie clip
var myMC:MovieClip = new MovieClip();
//let myMC dispatch the event MouseEvent.CLICK
myMC.dispatchEvent(new Event(MouseEvent.CLICK));
//add an event listener to myMC, that listens to the event MouseEvent.CLICK, and will call clickHandler
myMC.addEventListener(MouseEvent.CLICK, clickHandler);
//define the handler function
function clickHandler (event:Event){
trace("I heard the event MouseEvent.CLICK");
}
希望你已经认识到错在哪里了:事件是在监听器添加到myMC上之前触发的。这时候再添加事件监听器已经晚了,事件已经发生过了。幸运的是这很好解决,只用改变一下顺序,先添加事件监听器,然后再触发事件:
//create our myMC movie clip
var myMC:MovieClip = new MovieClip();
//add an event listener to myMC, that listens to the event MouseEvent.CLICK, and will call clickHandler
myMC.addEventListener(MouseEvent.CLICK, clickHandler);
//let myMC dispatch the event MouseEvent.CLICK
myMC.dispatchEvent(new Event(MouseEvent.CLICK));
//define the handler function
function clickHandler (event:Event){
trace("I heard the event MouseEvent.CLICK");
}
我们为什么要做这个测试呢?因为你很可能会遇到这样的问题,并且要找出问题所在可能会花一番功夫。提前在这里指出这个问题并教你怎样解决。
第9步:事件参数
事件处理函数有一个参数:事件参数。这个参数包含事件本身和事件触发者的数据信息。这个参数包含一些我们需要介绍的属性。下面是一些经常要用到的参数列表:
1, target:这个属性返回目标对象,也就是事件的触发者。
2, currentTarget:这个属性返回事件流中的当前目标对象,我们将在第10步讨论事件流。
3, type:这个属性返回事件字符串。这个值多用小驼峰书写,MOUSE_DOWN的值为mouseDown。
使用这个,我们可以对不同类型的事件使用同一个处理器。怎么做呢?我们会在下一步讨论。
第10步:多个事件,一个监听方法
先让我们来看一段代码:
stage.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, upHandler);
function downHandler(event:MouseEvent){
trace("Down");
}
function upHandler(event:MouseEvent){
trace("Up");
}
我们使用了两个事件,名为MouseEvent.MOUSE_DOWN和MouseEvent.MOUSE_UP。第一个事件是在鼠标的按钮被按下并保持按下时触发。当你释放这个按钮时,事件MouseEvent.MOUSE_UP触发。释放后,鼠标按钮弹起。
现在我们可以使用一个处理器来取代两个处理器(一个叫downHandler,一个叫upHandler)。删除刚才代码,写下面的代码:
stage.addEventListener(MouseEvent.MOUSE_DOWN, handler);
stage.addEventListener(MouseEvent.MOUSE_UP, handler);
function handler(event:MouseEvent){
trace("Something has occurred...");
}
好了,现在已经创建了处理器并且正常工作了,但是我们想让处理器可以根据捕获的事件来做不同的事情。幸运的是,我们可以使用event.type。具体使用如下:
stage.addEventListener(MouseEvent.MOUSE_DOWN, handler);
stage.addEventListener(MouseEvent.MOUSE_UP, handler);
function handler(event:MouseEvent){
if(event.type == "mouseDown"){
trace("Down");
}else{
trace("Up");
}
}
第11步:事件流
现在假设一个点击事件发生在一个影片剪辑上,命名为mcA。这个事件并不是简单的分配给mcA,而是流经整个播放器。这个事件流动的机制称为“事件流”,想一想事件是怎样在播放器中流动的。
事件从“舞台”的顶层开始,然后流经mcA的父节点,直到mcA。再从mcA向回“冒泡”,回到“舞台”。
太酷了,但是有什么用呢?因为现在我们知道一个事件会流经触发者的所有父节点,就可以使用一个事件监听器,捕获多个对象触发的事件。
第12步:多个对象,一个事件监听器
好了,让我们在一些多影片剪辑里创建影片剪辑。你可以自己做,也可以直接下载提供的文件。
我们创建3个影片剪辑,并将其实例取名为redMC,blueMC和greenMC。然后将他们都放在一个更大的名为container的影片剪辑中。
现在,我们开始写代码。我已经创建了一个名为Actions的层,你需要在这个层里写代码。首先,为container添加事件监听器,监听MouseEvent.CLICK事件,事件处理器名为clickHandler。
container.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(event:MouseEvent){
//function body
}
我们想知道是哪个按钮被点击了,那为什么要给container添加事件监听器呢?如下图所示:
你可以看到,事件是被redMC触发的,然而事件还是要通过“冒泡”回到container的。container添加的事件监听器可以监听到这个事件,会调用监听器方法clickHandler。对于blueMC和greenMC也是如此。
下面我们要使用event.target,因为event.target是事件的触发者,这里事件触发者是redMC。那么我们怎样使用event.target呢?我们可以验证event.target.name,这会将实例的名字以string类型返回。因此我们可以使用普通的if语句:
container.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(event:MouseEvent){
if(event.target.name == "redMC"){
trace("Red");
}
if(event.target.name == "blueMC"){
trace("Blue");
}
if(event.target.name == "greenMC"){
trace("Green");
}
}
第13步:常用的事件
现在你已经对事件框架有了很好的理解,然而有些时候为实现某些功能,最重要的是知道该用哪个事件。要查找所有的事件,可以访问ActionScript 3.0语言和组件参考。只需要点击事件容器类,例如MouseEvent,看看都有什么事件。
第14步:MouseEvent.MOUSE_MOVE
这个事件在用户移动鼠标的时候发生。如果你为名为myMC的影片剪辑添加这个事件监听器,你就会知道鼠标什么时候划过myMC。
myMC.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
function mouseMoveHandler(event:MouseEvent){
trace("Your mouse is moving over myMC");
}
第15步:MouseEvent.MOUSE_OVER
这个事件在用户将鼠标移入到对象上面时触发。此事件只在用户将光标由另一个对象移动到本对象上时触发。在本对象上移动鼠标就不再触发MouseEvent.MOUSE_OVER事件了,而会触发MouseEvent.MOUSE_MOVE事件。
myMC.addEventListener(MouseEvent.MOUSE_OVER, overHandler);
function overHandler(event:MouseEvent){
trace("You just moved the mouse on myMC");
}
第16步:MouseEvent.MOUSE_OUT
这个事件正好与MouseEvent.MOUSE_OVER事件相反。当用户的光标移出对象时触发。
myMC.addEventListener(MouseEvent.MOUSE_OUT, outHandler);
function outHandler(event:MouseEvent){
trace("You just moved the mouse out myMC");
}
第17步:MouseEvent.MOUSE_DOWN
这个事件会在鼠标的主键被按下,或者一直被按下时触发。
myMC.addEventListener(MouseEvent.MOUSE_DOWN, downHandler);
function downHandler(event:MouseEvent){
trace("The primary mouse button is pressed down on myMC");
}
第18步:MouseEvent.MOUSE_UP
这个事件正好与MouseEvent.MOUSE_DOWN事件相反。当用户释放鼠标主键时,事件MouseEvent.MOUSE_UP触发。
myMC.addEventListener(MouseEvent.MOUSE_UP, upHandler);
function upHandler(event:MouseEvent){
trace("The primary mouse button has been released, while it was hovering over myMC");
}
第19步:MouseEvent.CLICK
此事件的名字已经很清楚地表明了事件什么时候被触发,这个事件在用户点击鼠标按键时触发。
myMC.addEventListener(MouseEvent.CLICK, clickHandler);
function clickHandler(event:MouseEvent){
trace("You just clicked myMC");
}
第20步:MouseEvent.DOUBLE_CLICK
这个事件在用户双击鼠标按钮时触发。值得注意的是当MouseEvent.DOUBLE_CLICK事件触发时,MouseEvent.CLICK事件已经是第二次发生了。
myMC.addEventListener(MouseEvent.DOUBLE_CLICK, doubleClickHandler);
function doubleClickHandler(event:MouseEvent){
trace("You just double clicked myMC");
}
如果你现在测试你的影片,双击鼠标,什么都没有发生。为什么呢?默认情况下,影片剪辑(包括大部分的显示对象)将doubleClickEnable属性设置为false。因此MouseEvent.DOUBLE_CLICK事件不会被触发。只需要将其设置为true,一切都会正常工作。
myMC.addEventListener(MouseEvent.DOUBLE_CLICK, doubleClickHandler);
function doubleClickHandler(event:MouseEvent){
trace("You just double clicked myMC");
}
myMC.doubleClickEnabled = true;
第21步:Event.ENTER_FRAME
这个事件会在对象进入下一帧时触发(是的,听起来有点奇怪)。基本上这个事件触发的频率与帧率相同。意思是,如果你的影片帧率为30 fps,这个事件每秒会调用30次。这个事件用来干什么呢?你可以使用这个事件让物体渐渐变化。例如,你能以帧率让一个对象的横坐标增加5。
myMC.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
function enterFrameHandler(event:Event){
myMC.x += 5;
}
第22步:Event.COMPLETE
这个事件会在对象完成自己正在做的事情后触发。大多数情况下,你将在载入什么东西或者播放一些媒体格式的时候使用它。一个URLLoader会载入一个URLRequest,在URLLoader载入完成后,我们要将这些数据载入另一个loader中,再将其添加到舞台上。
var myURLRequest:URLRequest = new URLRequest("http://farm3.static.flickr.com/2382/1616598266_bafebf0086_o.jpg");
var myURLLoader:URLLoader = new URLLoader(myURLRequest);
myURLLoader.dataFormat = URLLoaderDataFormat.BINARY;
myURLLoader.addEventListener(Event.COMPLETE, completeHandler);
function completeHandler(event:Event){
var loader:Loader = new Loader();
loader.loadBytes(myURLLoader.data);
addChild(loader);
}
第23步:Event.RESIZE
当flash应用嵌入到的flash播放器或者页面的尺寸发生变化时,这个事件被触发。使用这个事件,你可以在尺寸变化后重新放置对象的位置。
stage.addEventListener(Event.RESIZE, resizeHandler);
function resizeHandler(event:Event) {
trace("The dimensions of the stage are "+stage.stageWidth+" x "+stage.stageHeight);
}
第24步:KeyboardEvent.KEY_DOWN
这个事件在键盘上的任何按键被按下时触发。
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
trace("You just pressed a key!");
}
第25步:KeyboardEvent.KEY_UP
这个事件与KeyboardEvent.KEY_DOWN事件正好相反,在任意按键被释放时触发。
stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
function keyUpHandler(event:KeyboardEvent){
trace("You just released a key");
}
第26步:使用内置的按键布尔值
当然,响应任意按键确实没有什么用(除了屏保),因此我们需要提取是哪个按键被按下的信息。幸运的是,一些按键被内置到KeyboardEvent类中了,它们都是布尔变量,当被按下时会被置为true。这些内置布尔变量是:
l KeyboardEvent.altKey,当alt键被按下时设置为true。
l KeyboardEvent.commandKey,当command键被按下时设置为true(只适合AIR)。
l KeyboardEvent.controlKey,当control(ctrl)键被按下时设置为true(只适合AIR)。
l KeyboardEvent.ctrlKey,在Windows系统中,当control(ctrl)键被按下时设置为true。但是在Mac系统中,当cmd键被按下时,ctrl键设置为true。
l KeyboardEvent.shiftKey,当shift键被按下时设置为true。
因此,在做一些事情之前,我们可以利用这些来确定哪个键被按下了。
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
if(event.shiftKey){
trace("You just pressed the shift key");
}
}
第27步:使用按键编码
你可能会问,那其它的按键呢?有一个概念叫做“按键编码”。每一个按键都有一个特定的编号:一个按键编码。我们可以验证触发事件的按键的按键编码。event.keyCode将返回一个整数,通过它可以实现这个功能。点击这里查看按键编码清单。虽然是用于javascript的,但是按键编码是相同的。
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
if(event.keyCode == 65){
trace("You just pressed the A key");
}
}
现在,很容易就可以将按键编码存储在一个变量中,直接使用这个变量,就不用按键编码了。
var A:uint = 65;
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
if(event.keyCode == A){
trace("You just pressed the A key");
}
}
第28步:使用字符编码
使用按键编码,大多数情况都能应付,然而有些时候并不是你需要的。例如,a和A字符使用的是相同的按键。但是,我们仍然想要区别这两个字符。事件带有事件触发者的字符编码。
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent) {
trace(event.charCode);
}
好了,一切正常,但是我们真的需要记住这些字符编码吗?不用,幸运的是我们可以使用charCodeAt()方法,将返回一个字符的字符编码(string类型)。charCodeAt()默认取字符串的第一个字符。charCodeAt(0)是第一个字符,charCodeAt(1)是第二个,以此类推。
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
if(event.charCode == String("a").charCodeAt()){
trace("You just pressed the lowercase a key");
}
}
第20步:聚焦
现在键入如下代码:
myMC.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
function keyDownHandler(event:KeyboardEvent){
trace("You just pressed a key, while being focused on myMC");
}
测试这段代码,无法正常工作!为什么?如果myMC是一个电影剪辑,它不会接受键盘输入,即键盘事件不会在影片剪辑上触发。如果你想让myMC响应,添加事件监听器到stage上,然后让myMC做一些事情。试着将myMC从影片剪辑改为动态文本域,就能正常工作了。
那么两个动态文本域呢?如果用户键入,两个文本域都会触发事件吗?不会,只有你正在键入的文本域会触发。这就叫做“聚焦”。键盘事件会被获得焦点的对象触发。舞台是唯一一个在其它对象获得焦点的同时,也能获得焦点的对象。
第30步:TimerEvent.TIMER
这个事件是专门为timers服务的。它在一个timer到达延迟事件时触发。意味着设定时间间隔,你可以使用这个事件做一些非常精确的事情。
var myTimer:Timer = new Timer(1000, 5);
myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER, timerHandler);
function timerHandler(event:TimerEvent){
trace("one second later...");
}
第31步:TimerEvent.TIMER_COMPLETE
所有的timer有一个可选的第二参数。这个参数用来设置timer的重复次数。意味着当timer到达延迟时间后,会再次启动。如果timer重复了设定的次数后,会触发事件TimerEvent.TIMER_COMPLETE。
var myTimer:Timer = new Timer(1000, 5);
myTimer.start();
myTimer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
function timerHandler(event:TimerEvent){
trace("The timer has repeated 5 times");
}
总结
教程到此为止!我希望你对ActionScript 3.0的事件框架有了大致理解。要明白讨论到的事件并不是现有的所有事件,我只是讨论了自己经常用到的事件。别忘了要经常查看语言和组件参考,会对你帮助很大!写这个教程我很荣幸!
个人总结:
之前一直有一个误区,认为ActionScript 3.0的事件流适用于所有的对象,事件流在父类和子类之间流动。其实事件流只存在于添加到显示列表上的显示对象,一般情况下是只有触发事件的对象才能监听到所触发的事件。熟悉设计模式的同志们都知道,Flash的显示列表是一个典型的组合设计模式的实例,父节点和子节点并不是继承的关系,而是组合关系。
事件是ActionScript 3.0中对象之间通信的工具。显示对象对于一些鼠标键盘等事件都是自动触发的,不需要手动地调用dispatchEvent()方法。所有的显示对象都继承了EventDispatcher类,都可以触发事件。如果是自定义的一个类想要触发事件必须继承EventDispatcher类,触发事件时也需调用dispatchEvent()方法。