内容来源自:http://flash.9ria.com/thread-16717-1-2.html
(一)什么是事件?
让我们来举几个例子让大家明白其中的道理
举例一--天地会会员注册量超过20000
事件类型:天地会新闻
事件类型再细分: 有关注册量的新闻
事件时间:2008年11月27日
事件敏感数字: 20000
举例二---鼠标滚轮向上滑动
事件类型:MouseEvent
事件类型再细分:滚轮动作
事件信息:向上滚动(dlta>0)
举例三---用户按下字母键F
事件类型:keyBoardEvent
事件类型再细分:键盘被按下
事件信息 :keyCode=45
由上面三个例子可以看出,事件没什么好神奇的,事件不过就是新闻或者消息罢了。
新闻有政治的,有八卦的,有科技的,也有经济的。所以事件也可以分类,有鼠标事件、键盘事件、加载完成等等事件。
新闻(消息)有发生时间,发生地点,相关人物,所以AS3的事件里面也包含各种信息。鼠标事件里面可以有鼠标点了哪一个Sprite,鼠标点击处的坐标等信息、而键盘事件里面则可以有键盘是被按下还是被弹起,按下键的ASCII值,以及Ctrl是否被按下等信息。
- 经典语录:一切都是对象(All is object)
复制代码
一个数组是一个数组对象,一个影片剪辑是一个影片剪辑对象,甚至还可以说一个int值也是一个int对象,所以把AS3里面的事件叫做事件对象就不足为奇了。大家不要把事件对象理解成抛出事件的对象或者事件流经的对象,"事件对象"是一个消息。事件对象里面包含事件的相关信息。为了容易理解,在这里我们暂时把事件对象比喻成一个信封(事件对象),信封里面装着信(事件对象的具体信息)。
(二)什么是目标对象(target),当前目标对象(currentTarget)?
首先要知道所有的事件都是由FlashPlayer的事件管理中心发出的,当鼠标点击一个Sprite时,FlashPlayer会向这个Sprite发送一个鼠标事件对象(MouseEvent)从而形成一个事件流,而同在舞台的另一个Sprite则不会收到FlashPlayer发出的事件,因为事件流是流向前一个Sprite的不会经过第二个Sprite。
熟悉AS2的人知道,在AS2时代很多在内层的MovieClip都被剥夺了响应鼠标的权力。AS3为我们提供了强大的DOM事件流机制,使得所以对象都有平等的权力接收到相关事件信息。
事件流的三个阶段:
- 1 捕获阶段 (EventPhase.CAPTURING_PHASE)包括从舞台到目标节点范围内的所有节点
- 2 目标阶段 (EventPhase.AT_TARGET)权包括目标节点
- 3 冒泡阶段 (EventPhase.BUBBLING_PHASE)从目标节点的父节点返回到舞台的行程中遇到的节点
复制代码
不是所有的事件都有这三个阶段。如Timer、URLLoader,它们的事件对象将直接派送给目标对象(target).它们只包含目标阶段而没有捕获阶段和冒泡阶段。它们不会像显示对象容器(DisplayObjectContainer)那样有可能被一个DisplayObjectContainer对象包含或者自己包含一个DisplayObjectContainer对象,它们往往是单独存在的。
对于可显示对象我们应建立显示对象列表的概念,一个即使对象有大小有颜色,如果不被addChild到舞台(或者已经在舞台上的显示对象)里是不会显示的,这样FlashPlayer在渲染时候只需要渲染显示对象列表中的对象即可,可以大大节约资源。
当一个显示对象不在显示列表中时,FlashPlaye会把事件直接派送给它,这个时候就没有事件流,也没有捕获阶段和冒泡阶段,only目标阶段。
例如:
- var i=0;
- var s = new Sprite();
- s.addEventListener(Event.ENTER_FRAME,onEnter);
- function onEnter(evt:Event) {
- i++;
- s.name=i;
- trace(s.name);
- }
复制代码
对象不在显示列表中也可以接收事件对象(消息)。
但是当多个显示对象重叠的时候,经典的事件流出现了。(附图一)
首先用代码生成附图一
//outer
var outer = new Sprite();
outer.name="Outer";
outer.graphics.beginFill(0xFF0000);
outer.graphics.drawRect(0,0,100,100);
//mid
var mid = new Sprite();
mid.name="mid"
mid.graphics.beginFill(0x00FF00);
mid.graphics.drawRect(0,0,50,50);
//inner
var inner = new Sprite();
inner.name="inner";
inner.graphics.beginFill(0x0000FF);
inner.graphics.drawRect(0,0,25,25);
//addChild
outer.addChild(mid);
mid.addChild(inner);
addChild(outer);
outer.x=outer.y=100;
//添加侦听器
outer.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
inner.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
mid.addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
function mouseDownHandler(evt:MouseEvent){
trace("事件流当前阶段:"+evt.eventPhase)
trace("鼠标点击处最内层的显示对象(target)是:" + evt.target.name)
trace("事件当前流经显示对象(currentTarget)是:"+evt.currentTarget.name)
trace("===============================================================")
}
鼠标点击蓝色部分时的输出:
- 事件流当前阶段:2
- 鼠标点击处最内层的显示对象(target)是:inner
- 事件当前流经显示对象(currentTarget)是:inner
- ===============================================================
- 事件流当前阶段:3
- 鼠标点击处最内层的显示对象(target)是:inner
- 事件当前流经显示对象(currentTarget)是:mid
- ===============================================================
- 事件流当前阶段:3
- 鼠标点击处最内层的显示对象(target)是:inner
- 事件当前流经显示对象(currentTarget)是:Outer
- ===============================================================
复制代码
由附图一我们可以看到三个不同的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用来显示按钮名称(附图二),代码如下:
var button=new Sprite();
button.graphics.beginFill(0xFF0000);
button.graphics.drawRect(0,0,50,25);
var t=new TextField();
t.text="START";
button.addChild(t);
addChild(button);
button.x=button.y=100;
//button.mouseChildren=false
addEventListener(MouseEvent.MOUSE_DOWN,mouseDownHandler);
function mouseDownHandler(evt:MouseEvent) {
evt.target.visible=false;
}
我们的本意是在按下START按钮后让其消失,但是如果我们的鼠标点到了TextField上时,发现消失的是TextField而不是button。因为鼠标点击处最内层的对象也就目标对象target是t(TextField对象)而不是button(Sprite对象),很多时候新手会被这种情况搞得一头雾水,如何解决这一问题?只要把button的mouseChildren属性设为false即可让其子对象不参与响应鼠标事件,这样便可以解决target和currentTarget混淆的问题。如果子对象不需要参与鼠标事件响应,完全可以把容器的mouseChildren属性设为false,还可以节约资源!
(三)如何处理事件对象?
由上面的例子还可以看出,在显示对象列表中的每一个对象都会有两次机会收到同一个事件对象(那封信)。只不过我们在注册侦听器时使用addEventListener(MouseEvent.MOUSE_DOWN,listener)时默认不在捕获阶段响应(useCapture=false);
在这两次收到事件的时候,注册了事件类型的对象的侦听器会被触发。
注册侦听器的方法:
由上面的例子还可以看出,在显示对象列表中的每一个对象都会有两次机会收到同一个事件对象(那封信)。只不过我们在注册侦听器时使用addEventListener(MouseEvent.MOUSE_DOWN,listener)时默认不在捕获阶段响应(useCapture=false);
在这两次收到事件的时候,注册了事件类型的对象的侦听器会被触发。
注册侦听器的方法:
- 对象.addEventListener(事件类型,侦听器)
复制代码
其中侦听器可以是函数或者类的方法(类的方法其实也是函数)。
在侦听器中,我们需要作出相应的动作,下面一个Mp3播放器为例(例图三)
假设播放器中有一个进度条ProgressBar,首先为ProgressBar注册一个侦听器
ProgressBar.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
当用户点击进度条时触发了一个MouseEvent.MOUSE_DOWN事件,那么按前面所讲的ProgressBar会收到这个事件。ProgressBar接收到这个事件后onMouseDown函数会被调用,onMouseDown函数要做两件事
1.改变ProgressBar蓝色部分的长度来更新进度。
2.改变当前Mp3的播放位置。
通学的伪码写法是:
function onMouseDown(evt:MouseEvent){
ProgressBar.progress=.....;//伪码
mp3.play(ProgressBar.progress);//伪码
}
上面的方法看似没有问题,但是OOP的思想告诉我们,Mp3的播放位置的控制应该由Mp3控制核心来控制,而不是由ProgressBar来控制。所以可以改进上述代码
function onMouseDown(evt:MouseEvent){
ProgressBar.progress=.....;//伪码
dispatchEvent(new ProgressChangeEvent(ProgressBar.progress))//发送一个进度事件,告诉控制器,进度改变了
} |