事件的默认行为 与 事件流
默认行为
开发人员通常负责编写响应事件的代码。但在某些情况下,行为通常与某一事件关联,使得 Flash Player 或 AIR 会自动执行该行为,除非开发人员添加了取消该行为的代码。由于 Flash Player 或 AIR 会自动表现该行为,因此这类行为称为默认行为。
例如,当用户在 TextField 对象中输入文本时,普遍期待文本显示在该 TextField 对象中,因此该行为被内置到 Flash Player 和 AIR 中。如果您不希望该默认行为发生,可以使用新的事件处理系统来取消它。当用户在 TextField 对象中输入文本时,Flash Player 或 AIR 会创建 TextEvent 类的实例以表示该用户输入。若要阻止 Flash Player 或 AIR 显示 TextField 对象中的文本,必须访问该特定 TextEvent 实例并调用该实例的 preventDefault() 方法。
并非所有默认行为都可以被阻止。例如,当用户双击 TextField 对象中的单词时,Flash Player 和 AIR 会生成一个 MouseEvent 对象。无法阻止的默认行为是:加亮鼠标点击的单词。
许多类型的事件对象没有关联的默认行为。例如,当建立网络连接时,Flash Player 调度一个连接事件对象,但没有与该对象关联的默认行为。Event 类及其子类的 API 文档列出了每一类型的事件,并说明所有关联的默认行为,以及是否可以阻止该行为。
默认行为仅与由 Flash Player 或 AIR 所调度的事件对象关联,但通过 ActionScript 以编程方式调度的事件对象则不存在默认行为。了解这一点很重要。例如,可以使用 EventDispatcher 类的方法来调度类型为textInput 的事件对象,但该事件对象没有关联的默认行为。也就是说,Flash Player 和 AIR 不会因为您以编程方式调度了 textInput 事件而在 TextField 对象中显示字符。
cancelable 属性 事件返回一个布尔值。如果用 preventDefault() 方法可以取消与事件关联的默认动作,则为 true,否则为 fasle。
事件流
只要发生事件,Flash Player 或 AIR 就会调度事件对象。如果事件目标不在显示列表中,则 Flash Player 或 AIR 将事件对象直接调度到事件目标。例如,Flash Player 将 progress 事件对象直接调度到 URLStream 对象。但是,如果事件目标在显示列表中,则 Flash Player 将事件对象调度到显示列表,事件对象将在显示列表中穿行,直到到达事件目标。
“事件流”说明事件对象如何在显示列表中穿行。显示列表以一种可以用树结构来描述的层次形式进行组织。位于显示列表层次顶部的是舞台,它是一个特殊的显示对象容器,用作显示列表的根。舞台由 flash.display.Stage 类表示,且只能通过显示对象访问。每个显示对象都有一个名为 stage 的属性,该属性表示应用程序的舞台。
当 Flash Player 或 AIR 为显示列表相关的事件调度事件对象时,该事件对象将进行一次从舞台到“目标节点”的往返行程。DOM 事件规范将目标节点定义为代表事件目标的节点。也就是说,目标节点是发生了事件的显示列表对象。例如,如果用户单击名为 child1 的显示列表对象,Flash Player 或 AIR 将使用 child1 作为目标节点来调度事件对象。
从概念上来说,事件流分为三部分。第一部分称为捕获阶段,该阶段包括从舞台到目标节点的父节点范围内的所有节点。第二部分称为目标阶段,该阶段仅包括目标节点。第三部分称为冒泡阶段。冒泡阶段包括从目标节点的父节点返回到舞台的行程中遇到的节点。
如果将显示列表想像为一个垂直的层次,其中舞台位于顶层(如下图显示),那么这些阶段的名称就更容易理解了:
如果用户单击 Child1 Node,Flash Player 或 AIR 将向事件流调度一个事件对象。如下面的图像所示,对象的行程从舞台开始,向下移到父节点,然后移到 Child1 节点,再“冒泡”返回到舞台(即在行程中重新经过父节点,再返回到舞台)。
在此示例中,捕获阶段在首次向下行程中包括舞台和父节点。目标阶段包括在 Child1 花费的时间。冒泡阶段包括在向上返回到根节点的行程中遇到的父节点和舞台。
事件流使现在的事件处理系统比 ActionScript 程序员以前使用的事件处理系统功能更为强大。早期版本的 ActionScript 中没有事件流,这意味着事件侦听器只能添加到生成事件的对象。在 ActionScript 3.0 中,您不但可以将事件侦听器添加到目标节点,还可以将它们添加到事件流中的任何节点。
当用户界面组件包含多个对象时,沿事件流添加事件侦听器的功能十分有用。例如,按钮对象通常包含一个用作按钮标签的文本对象。如果无法将侦听器添加到事件流,您将必须将侦听器添加到按钮对象和文本对象,以确保您收到有关在按钮上任何位置发生的单击事件的通知。而事件流的存在则使您可以将一个事件侦听器放在按钮对象上,以处理文本对象上发生的单击事件或按钮对象上未被文本对象遮住的区域上发生的单击事件。
不过,并非每个事件对象都参与事件流的所有三个阶段。某些类型的事件(例如 enterFrame 和 init 类型的事件)会直接调度到目标节点,并不参与捕获阶段和冒泡阶段。其他事件可能以不在显示列表中的对象为目标,例如调度到 Socket 类的实例的事件。这些事件对象也将直接流至目标对象,而不参与捕获和冒泡阶段。
要查明特定事件类型的行为,可以查看 API 文档或检查事件对象的属性。下面的部分介绍了如何检查事件对象的属性。
了解 Event 类的属性
Event 类定义许多只读属性和常量,以提供有关事件对象的重要信息。以下内容尤其重要:
-
事件对象类型由常量表示,并存储在 Event.type 属性中。
-
事件的默认行为是否可以被阻止由布尔值表示,并存储在 Event.cancelable 属性中。
-
事件流信息包含在其余属性中。
事件对象类型
每个事件对象都有关联的事件类型。数据类型以字符串值的形式存储在 Event.type 属性中。知道事件对象的类型是非常有用的,这样您的代码就可以区分不同类型的对象。例如,下面的代码指定 clickHandler() 侦听器函数应响应传递给 myDisplayObject 的任何鼠标单击事件对象。
myDisplayObject.addEventListener(MouseEvent.CLICK, clickHandler);
大约有 20 多种事件类型与 Event 类自身关联并由 Event 类常量表示,其中某些数据类型显示在摘自 Event 类定义的以下代码中:
package flash.events { public class Event { // class constants public static const ACTIVATE:String = "activate"; public static const ADDED:String= "added"; // remaining constants omitted for brevity } }
这些常量提供了引用特定事件类型的简便方法。您应使用这些常量而不是它们所代表的字符串。如果您的代码中拼错了某个常量名称,编译器将捕获到该错误,但如果您改为使用字符串,则编译时可能不会出现拼写错误,这可能导致难以调试的意外行为。例如,添加事件侦听器时,使用以下代码:
myDisplayObject.addEventListener(MouseEvent.CLICK, clickHandler);
而不是使用:
myDisplayObject.addEventListener("click", clickHandler);
默认行为信息
代码可通过访问 cancelable 属性来检查是否可以阻止任何给定事件对象的默认行为。cancelable 属性包含一个布尔值,用于指示是否可以阻止默认行为。您可以使用 preventDefault() 方法阻止或取消与少量事件关联的默认行为。有关详细信息,请参阅了解 Event 类的方法下的“取消默认事件行为”。
事件流信息
其余 Event 类属性包含有关事件对象及其与事件流的关系的重要信息,如以下列表所述:
-
bubbles 属性包含有关事件流中事件对象参与的部分的信息。
-
eventPhase 属性指示事件流中的当前阶段。
-
target 属性存储对事件目标的引用。
-
currentTarget 属性存储对当前正在处理事件对象的显示列表对象的引用。
bubbles 属性
如果事件对象参与事件流的冒泡阶段,则将该事件称为“冒泡”,这指的是从目标节点将事件对象往回传递,经过目标节点的父节点,直到到达舞台。Event.bubbles 属性存储一个布尔值,用于指示事件对象是否参与冒泡阶段。由于冒泡的所有事件还参与捕获和目标阶段,因此这些事件参与事件流的所有三个阶段。如果值为 true,则事件对象参与所有三个阶段。如果值为 false,则事件对象不参与冒泡阶段。
eventPhase 属性
您可以通过调查任何事件对象的 eventPhase 属性来确定事件阶段。eventPhase 属性包含一个无符号整数值,该值代表三个事件流阶段中的一个阶段。Flash Player API 定义了单独的 EventPhase 类,该类包含三个对应于三个无符号整数值的常量,如以下摘录代码中所示:
package flash.events { public final class EventPhase { public static const CAPTURING_PHASE:uint = 1; public static const AT_TARGET:uint = 2; public static const BUBBLING_PHASE:uint= 3; } }
这些常量对应于 eventPhase 属性的三个有效值。使用这些常量可以使您的代码可读性更好。例如,如果要确保仅当事件目标在目标阶段中时才调用名为 myFunc() 的函数,您可以使用以下代码来测试此条件:
if (event.eventPhase == EventPhase.AT_TARGET) { myFunc(); }
target 属性
target 属性包含对作为事件目标的对象的引用。在某些情况下,这很简单,例如当麦克风变为活动状态时,事件对象的目标是 Microphone 对象。但是,如果目标在显示列表中,就必须考虑显示列表层次。例如,如果用户在包括重叠的显示列表对象的某一点输入一个鼠标单击,则 Flash Player 和 AIR 始终会选择距离舞台层次最深的对象作为事件目标。
对于复杂的 SWF 文件,特别是那些通常使用更小的子对象来修饰按钮的 SWF 文件,target 属性可能并不常用,因为它通常指向按钮的子对象,而不是按钮。在这些情况下,常见的做法是将事件侦听器添加到按钮并使用 currentTarget 属性,因为该属性指向按钮,而 target 属性可能指向按钮的子对象。
了解 Event 类的方法
有三种类别的 Event 类方法:
-
实用程序方法:可以创建事件对象的副本或将其转换为字符串
-
事件流方法:用于从事件流中删除事件对象
-
默认行为方法:可阻止默认行为或检查是否已阻止默认行为
Event 类实用程序方法
Event 类中有两个实用程序方法。clone() 方法用于创建事件对象的副本。toString() 方法用于生成事件对象属性的字符串表示形式以及它们的值。这两个方法都由事件模型系统在内部使用,但对开发人员公开以用于一般用途。
对于创建 Event 类的子类的高级开发人员来说,必须覆盖和实现两个实用程序方法的版本,以确保事件子类正常使用。
停止事件流
可以调用 Event.stopPropagation() 方法或 Event.stopImmediatePropagation() 方法来阻止在事件流中继续执行事件对象。这两种方法几乎相同,唯一的不同之处在于是否允许执行当前节点的其他事件侦听器:
-
Event.stopPropagation() 方法可阻止事件对象移到下一个节点,但只有在允许执行当前节点上的任何其他事件侦听器之后才起作用。
-
Event.stopImmediatePropagation() 方法也可阻止事件对象移到下一个节点,但不允许执行当前节点上的任何其他事件侦听器。
调用其中任何一个方法对是否发生与事件关联的默认行为没有影响。使用 Event 类的默认行为方法可以阻止默认行为。
取消事件默认行为
与取消默认行为有关的两个方法是 preventDefault() 方法和 isDefaultPrevented() 方法。调用 preventDefault() 方法可取消与事件关联的默认行为。若要查看是否已针对事件对象调用了 preventDefault(),请调用 isDefaultPrevented() 方法;如果已经调用,该方法将返回值 true,否则返回值 false。
preventDefault() 方法仅在可以取消事件的默认行为时才起作用。可通过参考该事件类型的 API 文档或使用 ActionScript 检查事件对象的 cancelable 属性来确定是否属于这种情况。
取消默认行为对事件对象通过事件流的进度没有影响。使用 Event 类的事件流方法可以从事件流中删除事件对象。
Event 类的子类
对于很多事件,Event 类中定义的一组公共属性已经足够了。但是,Event 类中可用的属性无法捕获其他事件具有的独特的特性。ActionScript 3.0 为这些事件定义了 Event 类的几个子类。
每个子类提供了对该类别的事件唯一的附加属性和事件类型。例如,与鼠标输入相关的事件具有若干独特的特性,无法被 Event 类中定义的属性捕获。MouseEvent 类添加了 10 个属性,扩展了 Event 类。这 10 个属性包含诸如鼠标事件的位置和在鼠标事件过程中是否按下了特定键等信息。
Event 子类还包含代表与子类关联的事件类型的常量。例如,MouseEvent 类定义几种鼠标事件类型的内容,包括 click、doubleClick、mouseDown 和 mouseUp 事件类型。
正如事件对象下的“Event 类实用程序方法”一节所述,创建 Event 子类时,必须覆盖 clone() 和 toString() 方法以实现特定于该子类的功能。