作者:alan98 来源:蓝色理想   酷勤网收集 2008-04-16

摘要
  就算是 AS 2 的 OOP 专家也希望能略读下这一段,因为 AS 3.0 的工作原理确实发生了很大的变化。如果你说你从没用过类,那你就错了,只要你在 Flash 中写过代码,那么实际上就已经使用了类。类可以简单理解为一种对象

类和面向对象编程

类(Class)和面向对象(Object Oriented),对于有些读者来说可能还没接触过而有些读者可能已经在 AS (或其它语言) 中使用类很多年了,为了让大家都能学会,我会扼要的介绍一下这些基础知识。就算是 AS 2 的 OOP 专家也希望能略读下这一段,因为 AS 3.0 的工作原理确实发生了很大的变化。如果你说你从没用过类,那你就错了,只要你在 Flash 中写过代码,那么实际上就已经使用了类。类可以简单理解为一种对象, MovieClip 就是影片剪辑的类,而文本框、影片剪辑、按钮、字符串和数值等都有它们自己的类。

一个类最基本的两个部分:属性(数据或信息),行为(动作或它能做的事)。属性(Property)指用于保存与该类有关的信息变量,行为(Behavior)就是指函数,如果一个函数是这个类中的一部分,那么我们就称它为方法(Method)。

一个基本的类:

常用 Flash 的朋友都知道,我们可以在库中创建一个元件,用这个元件可以在舞台上创建出很多的实例。与元件和实例的关系相同,类就是一个模板,而对象(如同实例)就是类的一个特殊表现形式。下面来看一个类的例子:

package {
 public class MyClass {
  public var myProperty:Number = 100;
  public function myMethod() {
   trace("I am here");
  }
 }
}

先来说明一下这段代码。在这里有些新的知识,对于 AS 2 老手也如此:包的声明。包(Package) ,作用就是把相关的类进行分组。知道这一点就够了,我们不再进行深入的讨论,本书的示例甚至不会用到包。Package 这个关键字和一对大括号是必需有的,我们理解为默认包,紧随其后的就是类的定义。
另一个变化是 AS3.0 中的类拥有了访问关键字。访问关键字是指:一个用来指定其它代码是否可访问该代码的关键字。 public (公有类) 关键字指该类可被外部任何类的代码访问。本书中所有示例的类都是 public 的。在深入学习了 AS 3.0 后,我们会发现不是所有类都是公有的,甚至还有多重的类,这些内容超出了本书的谈论范围。

本例中我们可以看到,这个类的名字为 MyClass,后面跟一对大括号。在这个类中有两种要素,一个是名为 myProperty 的变量,另一个是名为 myMethod 的函数。

包(Package)

包主要用于组织管理类。包是根据类所在的目录路径所构成的,并可以嵌套多层。包名所指的是一个真正存在的文件夹,用 “.”进行分隔。例如,有一个名为 Utils 的类,存在于文件夹 com/ friendsofed/ makingthingsmove/ 中(使用域名作为包名是一个不成文的规定,目的是保证包名是唯一的)。这个类就被写成 com.friendsofed.makingthingsmove.Utils。
在 AS 2 中,使用整个包名来创建一个类,例如:

class com.friendsofed.makingthingsmove.Utils {
}
在 AS 3 中,包名写在包的声名处,类名写类的声名处,例如:
package com.friendsofed.makingthingsmove{
 public class Utils {
 }
}

导入(Import)

想象一下,每次要使用这个类的方法时都要输入 com.friendsofed.makingthingsmove.Utils,是不是太过烦琐太过死板了。别担心,import 语句可以解决这个问题。在这个例子中,可以把下面这句放在 package 中类定义的上面: import com.friendsofed.makingthingsmove.Utils;。

构造函数(Constructor)

构造函数是指一个名字与类名相同的方法。当该类被实例化时,该函数会被自动调用,也可以传入参数,例如:
首先,创建一个类:

package {
 public class MyClass {
  public function MyClass(arg:String) {
   trace("constructed");
   trace("you passed " + arg);
  }
 }
}

然后,假设工作在 Flash CS3 IDE(集成开发环境)中,在时间轴上创建该实例:

var myInstance:MyClass = new MyClass("hello");

结果输出:
constructed
you passed hello

继承(Inheritance)

一个类可以从另一个类中继承(inherit)和扩展(extend)而来。这就意味着它获得了另一个类所有的属性和方法(除了那些被 private 掩盖住的属性)。所生成的子类(派生类)还可以增加更多的属性和方法,或更改父类(基类)已有的属性或方法。要分别创建两个类来实现(两个独立的 .as文件),例如:

package {
 public class MyBaseClass {
  public function sayHello():void {
   trace("Hello from MyBaseClass");
  }
 }
}

package {
 public class MySubClass extends MyBaseClass {
  public function sayGoodbye():void {
   trace("Goodbye from MySubClass");
  }
 }
}

不要忘记,每个类都必须在其自身的文件中,文件名为该类的类名,扩展名 .as,所以必须要有 MyBaseClass.as 文件和 MySubClass.as 文件。因此,在使用 Flash CS3 IDE 时,保存的 FLA 文件,要与这两个类在同一个文件夹。
下面代码会生产两个实例,把它写入时间轴看看会发生什么:

var base:MyBaseClass = new MyBaseClass();
base.sayHello();
var sub:MySubClass = new MySubClass();
sub.sayHello();
sub.sayGoodbye();

第一个实例没什么可说的,值得注意的是第二个实例中的 sayHello 方法,虽然在 MySubClass 中没有定义 sayHello,但它却是继承自 MyBaseClass 类的。另一个值得注意的是,增加了一个新的方法 sayGoodbye,这是父类所没有的。
下面说说,在子类中如何改变一个父类中已存在的方法。在 AS 2 中,我们可以只需要重新定义这个方法就可以了。而在 AS 3 中,则必需明确地写出 override 关键字,来进行重新定义。

package {
 public class MySubClass extends MyBaseClass {
  override public function sayHello():void {
   trace("Hola from MySubClass");
  }
  public function sayGoodbye():void {
   trace("Goodbye from MySubClass");
  }
 }
}

请注意,原来的 sayHello 方法被重写,再调用 MySubClass 后,就有了新的信息。另外,私有成员也不能被重写,因为它们只能被它们自身的类访问。

MovieClip/Sprite 子类

我们可以自己写一个类,然后让另一个类去继承它。在 AS 3 中,所有代码都不是写在时间轴上的,那么它们一开始都要继承自 MovieClip 或 Sprite。 MovieClip 类是影片剪辑对象属性和方法的 ActionScript 模板。它包括我们所熟悉的属性如:影片的 x,y 坐标,缩放等,这些在 AS 3 中的变化不大。

AS 3 还增加了 Sprite 类,通常把它理解为不在时间轴上的影片剪辑。很多情况下,只使用代码操作对象,并不涉及时间轴和帧,这时就应该使用 Sprite 这个轻型的类。如果一个类继承自 MovieClip 或 Sprite ,那么它会自动拥有该类所有的属性和方法,我们还可以为这个类增加特殊的属性和方法。

例如,游戏设计一个太空船的对象,我们希望它拥有一个图形,并且在屏幕的某个位置移动,旋转,并为动画添加 enterFrame 侦听器,还有鼠标、键盘的侦听等。这些都可以由 MovieClip 或 Sprite 来完成,所以就要继承自它们。同时,还可以增加一些属性如:速度(speed)、油量(fuel)、损坏度(damage),还有像起飞(takeOff)、坠落(crash)、射击(shoot)或是自毁(selfDestruct)等方法。那么这个类大概是这样的:

package {
 import flash.display.Sprite;
 public class SpaceShip extends Sprite {
  private var speed:Number = 0;
  private var damage:Number = 0;
  private var fuel:Number = 1000;
  public function takeOff():void {
   //...
  }
  public function crash():void {
   //...
  }
  public function shoot():void {
   //...
  }
  public function selfDestruct():void {
   //...
  }
 }
}

注意,首先要导入 flash.display 包中的 Sprite 类,如果要导入MovieClip类,同样也需要导入这个相同的包 flash.display.MovieClip 类。

创建文档类(Document class)

现在我们对类已经了一定的了解,接下来,看看如果真正地使用它。有时候我常说基于 AS 3的 SWF 是多么的重要,这是因为 AS 3 引入了一个全新的概念,文档类(document class)。

一个文档类就是一个继承自 Sprite 或 MovieClip 的类,并作为 SWF 的主类。读取 SWF时,这个文档类的构造函数会被自动调用。它就成为了我们程序的入口,任何想要做的事都可以写在上面,如:创建影片剪辑,画图,读取资源等等。如果在 Flash CS3 IDE 中写代码,可使用文档类,也可以选择继续在时间轴上写代码。但如果使用 Flex Builder 2 或免费Flex SDK,那里没有时间轴,唯一的办法就是写在类中。这些工作一切都围绕着强大的文档类而展开,没有它就没有 SWF。以下是一个文档类的框架:

package {
 import flash.display.Sprite;
 public class Test extends Sprite {
  public function Test() {
   init();
  }
  private function init():void {
   // 写代码处
  }
 }
}

如果你看过前面的部分,不会认为这是个新知识,只不过是把他们放在了一起而已。使用默认包,导入并继承 Sprite 类。构造函数只有一句,调用 init 方法。当然,也可以把所有代码写在构造函数里,但是要养成一个好习惯,就是尽量减少构造函数中的代码,所以把代码写到了另一个方法中。本书会给大家很多代码块进行测试,那时要像上面这个例子一样把代码块放入 init 方法中,这样在影片编译执行时,就会调用 init 中的代码。下面我们要开始学习如何连接文档类和 SWF。

使用 Flash CS3 IDE(集成开发环境)

Flash CS3 IDE 是实现文档类的最方便的工具。把上述的类选择一个文件夹进行保存,文件名为 Test.as。打开 Flash CS3 ,创建一个 FLA 文件,保存到一这个类相同的目录下。确认 FLA 默认发布设置为 Flash Player 9 及 AS 3.0。在属性面板中,我们注意到出现了一个名为文档类(Document Class)的区域(图2-1)。只需输入类名:Test。


图2-1 设置文档类

请注意,我们输入的是类名,而不是文件名。所以这里不需要输入扩展名 .as。如果这个类包涵在一个包中,那么就需要输入类的完整路径——例如:com.friendsofed.chapter2.Test。

程序动画

下面,再来学习一些 AS 3 编程的基本原理。如果你已经选择好了一个开发环境,那么就出发吧。让我们进入 ActionScript 动画世界。

动画的执行过程

几乎所有的程序动画都包括几种不同的执行过程。对于逐帧动画来说,意味着创建和存储一组连续的位图,每一帧都是一幅图像,只需要进行显示即可,见图 2-3。


图2-3 逐帧动画

当我们在 Flash 中使用图形或元件时,事情就发了微妙的变化。这时,Flash 不会为每一帧创建和存储新的位图。对于每一帧而言,Flash 存储的是舞台上每个对象的位置,大小,颜色等等。比如,一个小球在屏幕上移动,每一帧只存储小球的在该帧上的位置,第1帧小球的位置在左边第10个像素,第2帧也许就在第15个像素,等等。Flash 播放器(Flash Player)读取这些信息,再根据这些信息的描述来渲染舞台并显示该帧。根据这些变化扩展一下流程图,见图 2-4。


图2-4 渲染并显示帧

我是这样描述一个动态程序动画的,见图2-5。


图2-5 脚本动画

如图2-5所示,没有第1帧、第2帧的概念,脚本动画通常只由一帧完成。下面我们就来看看动画的执行过程。

首先,建立初始化。舞台中放入一个影片剪辑,再创建补间动画,或使用代码来描述整个场景。总之,最后都要对该帧进行渲染及显示。
然后,应用自定义规则。规则可以像“让球向右移动5像素”这样简单,也可以是由几十条复杂的三角函数组成。使用自定义规则会产生新的描述再根据这些描述进行渲染及显示,并不断地应用这个规则。

请注意,同一规则被一遍又一遍地执行,而不是对第1帧用一套规则,而对第2帧又使用另一套规则。所以难度就在于,一套规则要处理所有可能出现的情况。要是球向右移动得过远,超出了舞台怎么办?你的这套规则就要解决这个问题。是否还希望用户通过鼠标来操作小球?那么你的规则也要把它考虑进去。
听起来很复杂,其实不然,这里所说的“规则”,实际上就是 ActionScript 代码。每套规则都可由一行或多行代码组成。下面是小球向右移动5像素的例子:
ball.x = ball.x + 5;
这句话是说无论小球 X 坐标(水平轴)在哪里,都在原来的 X 位置上增加5像素,并把该坐标作为它的新 X 坐标。也可简化为:
ball.x += 5;
“+=”操作符:把右边的值与左边的变量相加,相加的结果再赋值给该变量。以下是更多的高级规则,日后会学到:

var dx:Number = mouseX - ball.x;
var dy:Number = mouseY - ball.y;
var ax:Number = dx * spring;
var ay:Number = dy * spring;
vx += ax;
vy += ay;
vy += gravity;
vx *= friction;
vy *= friction;
ball.x += vx;
ball.y += vy;
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(ball.x, ball.y);
graphics.lineTo(mouseX, mouseY);

这段现在看不懂没关系,大家只要知道 Flash 会在每一帧中生成这段代码,并不断地执行。

怎样让它循环执行?看看我第一次的尝试,这也是很多 AS 初学者都会犯的错误。这是在很多程序设计语言中都存在的循环结构,如 for 和 while。用循环结构使代码重复执行,这就是我曾写的那段:

for (i = 0; i < 500; i++) {
 ball.x = i;
}

看起来相当简单。变量 i 从 0 开始,所以小球 X 坐标移动到 0——舞台最左边。i++ 让i 的值每次增长1,即:0~1~2~3~4…,每次这个值都会做为 ball.x 的值,把小球从左向右移动。当值为500时,表达式 i<500 值为假(false),循环结束。

如果你也犯过同样的错误,就会知道,小球没有在舞台上发生移动——只是一下子出现在了舞台的右边而已。为什么没有移动到中间的那些点上?其实它移动了,只是我们没有看到,因为我们没有让 Flash 去刷新屏幕。图2-6 为另一个流程图,看看实际都发生了什么。


图2-6 为什么循环结构无法产生动画

实际上我们使用自定义规则使球移动到指定位置,并创建了500次新的场景。但在循环结束之前没有给出显示,这是因为 Flash 只在每一帧结束后才进行一次刷新,这点很重要。以下是 Flash 进入帧的动作顺序:

  1. 在舞台上放置所有的对象,不论在何级,何层,或是否为加载的影片。
  2. 执行帧上所有的 Action 脚本(ActionScript),不论在何级,何层,不论处于影片剪辑还是按钮中,也不论它嵌套在何处。
  3. 判断是否到了该显示的时候。如果设置帧频为 20 帧/秒,Flash 最少要等上一帧显示后50毫秒后再进行下一次显示,显示了该帧后,就要编译和进入下一帧。如果帧频没有到 20 帧/秒,那么要等待到正确的时间再去执行。

定时时间存在着一些问题。首先,众所周知帧频是不精确的(即使在Flash 9中),不要依赖它作为精确的定时器。其次,在大量的编译和 AS 执行花费的时间会超出规定的时间。

尽管如此,我们也不必担心自己的脚本会被砍掉一部分。在进入第3步之前,Flash 会完成所有可执行代码(第2步),即使需要延缓帧频也要完成。Flash 为了能完成脚本,甚至会等上15秒。在上面的例子中,Flash 等待循环结束,然后进入下一帧,只在跳转到下一帧时进行屏幕的刷新。这就是为什么我们看到的是跳动而不是移动。因此,要想完成移动,我们所要做的就是打散这个循环,请回过头参考图 2-5。

帧循环

帧循环的理念,存在于 Flash 最早的版本中,那时 ActionScript 还不像今天那么强大。把代码写入关键帧,并在下一帧中写入像 gotoAndPlay 这样的语句,使播放头(playhead)回到前一帧。这样两帧之间就形成了一个无限循环,每当播放头到了代码帧上时,就会执行那些代码。例如,在舞台上有一个实例名为 ball 的影片剪辑。
第一帧的代码就像这样:

ball.x ++;

第二帧的代码如下:

gotoAndPlay(1);

实际上第二帧不需要做任何事,只是让时间轴自动回到第一帧而以。另一个版本是建立三个帧,第一帧进行初始化,写入只执行一次的代码,不进行循环。第二帧才是主要的执行代码,第三帧只写 gotoAndPlay(2); 这个方法在早期 Flash 版本中常被使用,虽然有点过时,但是同样可以出色地完成任务。马上我们还要学到更灵活更强大的设置方法,但今后你会发现其实原理上是一样的。

影片事件

影片事件在 AS 3 中彻底的消失了,这真是件好事。但还要捎带提一下,回顾 Flash 5 的时代,只有帧循环和影片剪辑事件两种选择。影片事件指代码直接写在影片剪辑上,而不是帧上。如何实现影片事件,首先选择舞台上的影片剪辑,然后打开动作面板并将代码写在上面,这些代码只对该影片剪辑有效。所有代码必需写在事件块中,比如:

onClipEvent(eventName){
// code goes here
}

对于 onClipEvent(eventName),作用于 eventName(某种事件)。对于"on"类型事件则必需指定鼠标或键盘事件,如按下(press)和释放(release)。

事件名称(eventName)是指许多Flash 影片事件之一,所谓事件就是在影片中发生的事。事件分为两种:系统事件和用户事件。系统事件指发生在如计算机,Flash,或影片上的事件,比如调取数据,调取信息,或播放帧等。用户事件是指用户所做的一些事,基本上就是鼠标和键盘两种。影片事件使用得最多的就是 load 和 enterFrame 这两个。 Load 事件会在影片第一次出现在舞台上时才执行,且只执行一次。所以说非常适合在这里面写入初始化代码。只要把代码写在大括号间即可:

onClipEvent(load){
// initialization code
}

我们可以把带有如下代码的影片剪辑放入时间轴上(注意:此处为 AS 1 写法):

onClipEvent (load) {
 this._x = 100;
 this._y = 100;
}
onClipEvent (enterFrame) {
 this._x += 5;
}

本书示例中的代码不使用这种写法(因为它已经不是一种语言了),但不论使用何种方法,初始化(initialization),重复动作(repeating actions)和屏幕刷新(screen refresh)都是非常重要的。

事件及事件处理

Flash MX 的 ActionScrpt 发生了重要的改变,这些转变与革新为 Flash 成为真正的富客户端程序(RIA)奠定了基础。其中一个就是全新的事件结构,在编写非常复杂的行为时比之前的版本好用很多。 Flash MX 之前的版本,只能把代码放在影片和按钮的 onClipEvent(eventName) 或 on(eventName) 这两种事件处理方法中。这就意味着,在设计的时候就要把影片剪辑放到舞台上,并把代码写入影片剪辑中。MX 的事件结构并不完美,但与之前版本来说已经有了长足的进步,并允许我们在任何时候访问任何事件,或是停止处理任何事件,或是动态改变某个事件的行为,可以想象这有多么的强大和灵活。

要想了解事件,就要明白下面几条概念:侦听器(lintener)与处理函数(handler),这两个名字很贴切,侦听器就是侦听事件的对象,处理函数是一个用于处理所要发生的事件的函数。侦听与处理在 ActionScript 的发展过程中进行过很多次演变,在 AS 2 中就有很多不同的实现方法。为了避免混乱,我很推崇 AS 3,因为它简化了这个过程,使事件处理变得更方便更一致。

事件侦听器与处理函数

前面说过,侦听器是一个用于侦听事件的对象。我们可以设计一个类,通过调用addEventListener 函数为某事件指定一个侦听器。输入要侦听的事件名称以及要执行处理的函数名称。看一个例子:

addEventListener("enterFrame", onEnterFrame);

在加入事件侦听器时,可使用可选参数,本书中不会用到;对于大多数的应用程序来说,会使用以上这种写法就够用了。请注意事件名"enterFrame"为字符串型,戏称它为“魔力字符串”(Magic String)。为什么这么叫?如果你误输入成了"entorFrame",尽管没有这个事件名称,编译器也会编译执行它,会发现事件处理函数没有执行。但 AS 3 仍会对其进行处理,除了使用“魔力字符串”以外,还可以使用事件类(Event Class)的属性。例如:

addEventListener(Event.ENTER_FRAME, onEnterFrame);

实际上 Event.ENTER_FRAME 的值就是”enterFrame”这个字符串。那么这个属性也可能输错就像 Event.ENTOR_FRAME ,但这种方法好在,如果输入错误了,程序会拒绝编译,并提示你在事件类中不存在该属性。编译器会提示发生错误的行及确切的字符。所以,最好使用这种方法,除非编译器会帮我们修正错误或编写代码。
除此之外,还有其它的事件类型如:MouseEvent.MOUSE_DOWN,KeyboardEvent.KEY_DOWN,TimerEvent.TIMER 等。这些都由 "mouseDown" , "keyDown" , "timer" 这样的简单字符串来表示,如果你记不住这些字符串,那么最好就去使用事件类的属性。

另一个重点是,使用 addEventListener 函数直接调用类中的函数。有时,需要侦听另一个对象产生的事件,例如,有一个名为 mySpriteButton 的 Sprite 影片(Sprite):影片或按钮,能完成按钮的动作。当用户点击它的时候就会产生 mouseDown(鼠标按下)事件。侦听该 Sprite 影片的 mouseDown 事件,就要调用该对象的 addEventListener 方法,如下:

mySpriteButton.addEventListener(MouseEvent.MOUSE_DOWN, onSpritePress);

最后一点,必需要有事件处理函数如 onEnterFrame,在 AS 3 中,可以任意地为事件处理函数命名,这点与以前的 ActionScript 不同。在 enterFrame 示例中,使用 onEnterFrame 做事件处理函数,是因为我们习惯使用这个名称。在 AS 3 中,onEnterFrame 已不再是关键字,当然也可以为这个处理函数命名为 move,run,或是 doSomethingCool。然而,我们已经习惯使用”on”表示事件开始,后面跟一些描述词如 onStartButtonClick,onConfigXMLLoad 或 onRoketCrash。有些朋友喜欢在事件名后面加上 "Handler" 作为后缀,如: enterFrameHandler,这只是个人偏好问题。

侦听器用于侦听事件,但对于一个侦听器来说,也许会同时侦听很多事件。在系统内部,一个事件对象拥有一个包括了所有对象及自身的侦听器的列表。如果一个对象能够产生多种不同类型的事件,如 mouseDown,mouseUp,mouseMove 等,那么它就拥有一个侦听器列表,其中包括它所涉及的所有类型的事件。无论触发何种事件,都会检索一遍列表,然后使列表中的每个对象都知道所发生的事件。
另一种对事件的描述是,将其看作一个加入到事件行列的侦听器成员。产生事件的对象将它所产生的事件公布给所有成员,当你不再需要这个对象进行侦听时,可以令其停止侦听或使用 removeEventListener 方法解除该成员;就是告诉对象从侦听器列表中删除该侦听器,这样一来,他就不会再接收信息了。

让我们看看这段代码,下面是一段在舞台中创建 Sprite 影片,并进行绘图,然后再为其添加侦听器的代码:

package {
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 public class EventDemo extends Sprite {
  private var eventSprite:Sprite;
  public function EventDemo() {
   init();
  }
  private function init():void {
   eventSprite = new Sprite();
   addChild(eventSprite);
   eventSprite.graphics.beginFill(0xff0000);
   eventSprite.graphics.drawCircle(0, 0, 100);
   eventSprite.graphics.endFill();
   eventSprite.x = stage.stageWidth / 2;
   eventSprite.y = stage.stageHeight / 2;
   eventSprite.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
   eventSprite.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
  }
  private function onMouseDown(event:MouseEvent):void {
   trace("mouse down");
  }
  private function onMouseUp(event:MouseEvent):void {
   trace("mouse up");
  }
 }
}

在初始化函数(init)中创建一个 Sprite 影片,并在里面画圆,置于舞台中心,最后两句是为它添加两个侦听器,侦听鼠标按下(MOUSE_DOWN)和鼠标弹起(MOUSE_UP)这两个事件。它们是MouseEvent 类的两个属性,而这个类必需要导入。最后定义两个处理函数 onMouseDown 和 onMouseUp。

由事件对象调用事件处理函数,通常还会包括一些事件信息。在处理鼠标事件时,就包括触发该事件时鼠标位置的信息如:鼠标点击在按钮上。对于键盘事件,就要包括按下键时的信息如 Ctrl,Alt,Shift等。把上述示例保存为 EventDemo.as 文件,并选择一种前面讲过的编译方式。当运行 SWF 时,就会看到每次点击或图形时,都会输出 pressed 或 released。

动画事件

我们希望能够使用代码让物体动起来,并允许屏幕反复地刷新。前面看过一个使用 enterFrame 影片事件的示例。现在把这种方法运用到 AS 3 中,只需要增加一个 enterFrame 事件的侦听器即可:

addEventListener(Event.ENTER_FRAME, onEnterFrame);

别忘了导入 Event 类,并创建一个名为 onEnterFrame 的方法。人们常常迷惑,只有一帧怎么能执行 enterFrame(进入帧) 事件呢?事实上,播放头并非真正地在进入下一帧,它只停留在第一帧上,并不是把播放头移动到下一帧才形成了 enterFrame 事件,而是用另一种方法:Flash 告诉播放头何时进行移动,可以把 enterFrame 看成一个定时器,只是有些不精确。
下面我们看看第一个 AS 3 动画:

package {
 import flash.display.Sprite;
 import flash.events.Event;
 public class FirstAnimation extends Sprite {
  private var ball:Sprite;
  public function FirstAnimation() {
   init();
  }
  private function init():void {
   ball = new Sprite();
   addChild(ball);
   ball.graphics.beginFill(0xff0000);
   ball.graphics.drawCircle(0, 0, 40);
   ball.graphics.endFill();
   ball.x = 20;
   ball.y = stage.stageHeight / 2;
   ball.addEventListener(Event.ENTER_FRAME, onEnterFrame);
  }
  private function onEnterFrame(event:Event):void {
   ball.x++;
  }
 }
}

init 函数创建了一个名为 ball 的 Sprite 影片,并为其建立事件侦听。 onEnterFrame 函数负责 ball 的运动及屏幕刷新工作。这是学习本书内容的基础,也是使用 ActionScript 创建动画的基础,所以务必要掌握。
 
显示列表

在 AS 3 之前,人们可以创建多个不同类型的可视化对象,包括影片剪辑,图形,按钮,文本框,位图,组件和基本形状。这些对象没有真正的层次结构,它们的创建、删除、操作方法也均不相同。比如,在 IDE 中,可以使用 attachMovie ,duplicateMovieClip 或 createEmptyMovieClip 的方法将影片剪辑放置于舞台上,文本框可以在开发环境中创建也可以用代码创建。而在使用位图(bitmap),视频(video)及组件(component)时,它们就像是来自于别的星球,最终被强硬地放在一起。

对于 AS 3 来说,这些对象都有了统一的归属。在舞台上所有可见的对象都继承自 DisplayObject 类。换句话讲,这些对象都是一个大家庭的成员,并以相同的形式工作,使用同样的方式进行创建,置入,删除,操作。无论创建 Sprite 影片,影片剪辑或文本框的方法都非常相近,我们需要使用 new 关键字来完成,创建任意类型的对象。为了证明这一点,请看下面三条示例:

var myTextfield:TextField = new TextField();
var myMovieClip:MovieClip = new MovieClip();
var mySprite:Sprite = new Sprite();

如果我们创建的是一个影片剪辑或 Sprite 影片的话,就可以直接里面进行绘制,如:

mySprite.graphics.beginFill(0xff0000);
mySprite.graphics.drawCircle(0, 0, 40);
mySprite.graphics.endFill();

但只有这些代码,还不能看到效果,这就引发了接下来要讨论的显示列表。“显示列表”是个新名词,可以理解为一颗由可视对象构成的树。舞台就是树根,默认为可见的,在舞台上,我们可以有很多影片剪辑或可视对象(文本框,图形等),把它们加入舞台后,也就成为可见的了。

这些影片中也许还嵌套着很多层的可视对象,这就是我们所谓的显示列表。 AS 2 与 AS 3 显示列表最大的不同在于,AS 2 中,当使用 attach 或 createEmptyMovieClip 方法创建影片剪辑时,必须指定它位于树的哪个位置。这样一来,影片剪辑要放置在列表的指定位置。当删除该影片时,同样也无法改变它在列表中的位置或在列表中移除它。

在 AS 3 中,创建了一些 Sprite 影片后,不会自动被加入显示列表。在上面的示例中我们发现,创建一个 Sprite 后,并不涉及父级影片(parent)或深度(depth)的问题,这样就可以在它没有加入视觉列表之前就对其进行操作了。说到舞台(Stage),可以把这些显示对象看作是幕后的演员,虽然看不到,但确实存在,并时刻准备着亮相的一刻,我们使用 addChild 方法把对象加入显示列表。将文档类作为树根,向里面加入孩子时,会自己被设置为可见的。
现在,在前面的例子中再加入创建 Sprite 对象以及 addChild 方法,如下:

var mySprite:Sprite = new Sprite();
mySprite.graphics.beginFill(0xff0000);
mySprite.graphics.drawCircle(0, 0, 40);
mySprite.graphics.endFill();
addChild(mySprite);

如果大家有兴趣试一下这段代码的话,请把它们写入前面所给的类框架的 init 函数中。请注意,绘制出的圆默认位置是0,0点,可以改变其 x 和 y 属性。还要注意,创建新影片时不再需要像 AS 2 那样去设置深度(depth)。虽然深度管理为自动执行,但我们还有指定深度或改变深度的方法,这部分等将来用到时再讲。
使用 removeChild 方法,从显示列表中删除一个对象,并以该对象的名字作为参数。第一,删除一个对象,不是去毁灭它,对象依然保持原样,只是暂时被移除,当再次被加入到显示列表中,对象仍保持原来的状态。换句话讲,如果显示对象里面绘制了图形,或是已加载了一些外部信息,那么将它重新加入显示列表后,就不必再去重绘或重载这些信息。第二,把该对象重新加入显示列表后,还可以为它指定处在显示列表中的位置,这就是我们所熟知的重定父级。

从一个影片剪辑中删除一个对象,再把它加载到另一个影片中剪辑中,并保持刚刚被删除时的状态,在以前是不可能完成的。事实上,有时并不需要去删除影片,因为,一个子对象只能有一个父级,把它加入到另一个父级中,就会自动从原来的父级中删除。请看下面示例:

package {
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 public class Reparenting extends Sprite {
  private var parent1:Sprite;
  private var parent2:Sprite;
  private var ball:Sprite;
  public function Reparenting() {
   init();
  }
  private function init():void {
   parent1 = new Sprite();
   addChild(parent1);
   parent1.graphics.lineStyle(1, 0);
   parent1.graphics.drawRect(-50, -50, 100, 100);
   parent1.x = 60;
   parent1.y = 60;
   parent2 = new Sprite();
   addChild(parent2);
   parent2.graphics.lineStyle(1, 0);
   parent2.graphics.drawRect(-50, -50, 100, 100);
   parent2.x = 170;
   parent2.y = 60;
   ball = new Sprite();
   parent1.addChild(ball);
   ball.graphics.beginFill(0xff0000);
   ball.graphics.drawCircle(0, 0, 40);
   ball.graphics.endFill();
   ball.addEventListener(MouseEvent.CLICK, onBallClick);
  }
  public function onBallClick(event:MouseEvent):void {
   parent2.addChild(ball);
  }
 }
}

该类中有三个 Sprite 对象:parent1, parent2, ball。 parent1, parent2 影片直接加入显示列表,并在影片中绘制了正方形。 Ball 影片被加入到 parent1,就相当于加入了显示列表并可见。当小球被点击时,它将被加入 parent2 影片中。请注意,没有改变 Ball 的 x,y 坐标的代码,之所以产生移动是因为 Ball 被加载到了不同位置的 Sprite 影片中。 Sprite 影片被删除后再被加入显示列表,Ball 仍会出现。

子类化显示对象

前面已经讲过生成 Sprite 或 MovieClip 类的子类,对某个类进行子类化是非常有用的。首先,大家可能对 AS 3 取消 attachMovieClip 功能感到十分惊呀,如果这样的话,我们怎么才能在 Flash CS3 IDE 库中取出影片剪辑元件放入舞台呢?答案是,使用一个继承自 MovieClip 或 Sprite 的类。为了能够好地解释这个问题,简单地介绍一下 IDE:

  1. 创建一个新的 FLA 文件,并在舞台上绘制一些图形。
  2. 选中图形按下 F8 键转换为元件。
  3. 在转换为元件窗口中输入一个名称,并设置为影片剪辑类型。
  4. 选择为 ActionScript 导出。

在以前的 Flash 版本中,可以自由地给出标识符或输入一个类名。而在 Flash CS3 中,标识符一栏不可用了,类一栏会自动地填入默认值。这里还多出了基类一栏,默认为 flash.display.MovieClip,这里也可以填入继承自 MovieClip 或 Sprite 的自定义类。

随意输入一个类名,不必担心没有这个类,然后点击确定。这个地方很有趣,Flash 找不到这个类,它就会自动生成一个类,并对其进行编译。并不是说 Flash 会创建一个 ActionScript 类文件,但它会在 SWF 中,生成一串字节代码表示一个继承自 Sprite 或 MovieClip 的类。除了继承了基类,它什么都不会做,但它已经与库中的元件连接上了。比如,你的类名为 Ball。在文档类或时间轴上,可以这么写:

var ball:Ball = new Ball();
addChild(ball);

这样就在舞台上创建了一个库中的元件,就像 AS 2 的 attachMovie 方法一样。我们要是能给出自定义的真正的类名及路径的话,那么就可以让元件附加很多功能。现在,我们跳出 Flash IDE 回到类的世界,看下一个示例。下面再看一个重定父级的示例,这里有一些重复的部分可以写入另一个类中。看一下示例,假设已经创建了一个名为 parent1 的 Sprite 实例,要里面绘制正方形:

parent1.graphics.lineStyle(1, 0);
parent1.graphics.drawRect(-50, -50, 100, 100);

下面再创建一个名为 parent2 的 Sprite 实例,同样也是绘制一个正方形。当然这个例子毫无意义,但它可以告诉我们 Sprite 的子类是多么的有用。首先,我们建立一个名为 ParentBox 的类,并继承自 Sprite,这样一来,就拥有了绘制正方形的代码:

package {
 import flash.display.Sprite;
 public class ParentBox extends Sprite {
  public function ParentBox() {
   init();
  }
  private function init():void {
   graphics.lineStyle(1, 0);
   graphics.drawRect(-50, -50, 100, 100);
  }
 }
}

然后,使用这个类创建两个 ParentBox,这样做比创建两个 Sprite 对象要好得多。

package {
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 public class Reparenting2 extends Sprite {
  private var parent1:ParentBox;
  private var parent2:ParentBox;
  private var ball:Sprite;
  public function Reparenting2() {
   init();
  }
  private function init():void {
   parent1 = new ParentBox();
   addChild(parent1);
   parent1.x = 60;
   parent1.y = 60;
   parent2 = new ParentBox();
   addChild(parent2);
   parent2.x = 170;
   parent2.y = 60;
   ball = new Sprite();
   parent1.addChild(ball);
   ball.graphics.beginFill(0xff0000);
   ball.graphics.drawCircle(0, 0, 40);
   ball.graphics.endFill();
   ball.addEventListener(MouseEvent.CLICK, onBallClick);
  }
  public function onBallClick(event:MouseEvent):void {
   parent2.addChild(ball);
  }
 }
}

作为 ParentBox 的实例,它们仍是 Sprite ,因此还可以再增加子影片, init 方法直接进行绘图。虽然这个示例价值不大,但可以让你学会这种思想,往后,会在书中看到更多更复杂的示例。你也许想自己动手创建一个球(Ball)类,用于绘制小球,虽然这么做不会减少代码里,但是当你的类变得十分复杂时,把功能代码分离到不同的类中,这绝对是个好办法,它比将所有类写在一起要好得多,同时还促进了代码的重用性。那么现在就去创建这个 Ball 类吧,在日后的学习中还要用到呢。

交互动画

最后介绍一下交互动画,这也许是大家读这本书的主要原因。如果不使用交互运动,那么只使用补间动画不就行了。在前面一章简单地提到过,用户交互动画基于用户事件,总的来说可以归结为鼠标事件和键盘事件,下面就来学习不同的用户事件及其处理函数。

鼠标事件

AS 3 中鼠标事件发生了显著的变化。在 AS 2 中,影片剪辑会自动添加鼠标侦听器。现在,要手动地为对象添加侦听器。在 AS 3 中鼠标指针经过显示对象时才能触发鼠标事件。在 AS 2 中,无论鼠标指针在哪里,只要执行 mouseDown 或 mouseMove 就会触发所有的影片剪辑。而现在, mouseUp 和 mouseDown 事件与 AS 2 中的 onPress 和 onRelase 等同。鼠标事件的名称是定义好的字符串,像我们之前所提到的,最好使用 MouseEvent 类的属性,以避免输入错误,下面是 MouseEvent 类中所有可用的鼠标事件属性:
CLICK
DOUBLE_CLICK
MOUSE_DOWN
MOUSE_MOVE
MOUSE_OUT
MOUSE_OVER
MOUSE_UP
MOUSE_WHEEL
ROLL_OUT
ROLL_OVER

创建下面这个类,来测试一下,这个类会输出发生在 Sprite 影片上的鼠标事件名称。

package {
 import flash.display.Sprite;
 import flash.events.MouseEvent;
 public class MouseEvents extends Sprite {
  public function MouseEvents() {
   init();
  }
  private function init():void {
   var sprite:Sprite = new Sprite();
   addChild(sprite);
   sprite.graphics.beginFill(0xff0000);
   sprite.graphics.drawCircle(0, 0, 50);
   sprite.graphics.endFill();
   sprite.x = stage.stageWidth / 2;
   sprite.y = stage.stageHeight / 2;
   sprite.addEventListener(MouseEvent.CLICK, onMouseEvent);
   sprite.addEventListener(MouseEvent.DOUBLE_CLICK,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_DOWN,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_MOVE,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_OUT,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_OVER,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_UP,onMouseEvent);
   sprite.addEventListener(MouseEvent.MOUSE_WHEEL,onMouseEvent);
   sprite.addEventListener(MouseEvent.ROLL_OUT,onMouseEvent);
   sprite.addEventListener(MouseEvent.ROLL_OVER,onMouseEvent);
  }
  public function onMouseEvent(event:MouseEvent):void {
   trace(event.type);
  }
 }
}

请注意,每个事件类型都使用了同一个处理函数,输出所触发的事件类型的名称。

鼠标位置

除了鼠标事件外,对于文档类还有两个非常重要属性用于表示鼠标当前的位置:mouseX 和 mouseY。请注意,影片剪辑的位置,返回的值是鼠标的位置与影片剪辑的注册点的相对位置。例如,有一个名为 sprite 的 Sprite 影片,在舞台的 100,100 位置,而鼠标的位置在 150,250,你会得到如下结果:
mouseX 为 150
mousey 为 250
sprite.mouseX 为 50
sprite.mouseY 为 150
请注意鼠标位置与影片位置的相对关系。

键盘事件

键盘事件已被 AS 3 划分到另一个区域中。例如,在 AS 2 中,影片剪辑会自动侦听键盘事件,但只在某种情况下才接收这些事件。所以,最好增加一个专门用来做侦听器的影片剪辑,有时,影片剪辑接收了多个事件但被看作是一个键盘事件,这样就不对了。在 AS 2 的组成框架中,很大部一部分都是为键盘交互服务的,比如 Flash Player 体系中的:tab(table)管理,焦点(focus)管理及在文本框中对于 Enter 键与 Table 键的处理等。现在好了,键盘事件的名称与鼠标事件的相似,都是定义好的字符串,也可为 KeyboardEvent 类的属性。只有两种:
KEY_DOWN
KEY_UP

我们可以在一个特殊的对象上侦听键盘事件,就像上面那个鼠标侦听的例子一样。为了实现这个功能,我们需要设置对象的焦点,以便能够捕获这些事件,可以这样写:

stage.focus = sprite;

在很多情况下,侦听键盘事件是否有焦点很有意义,实现它只需直接对舞台进行键盘侦听。下面看一个示例:

package {
 import flash.display.Sprite;
 import flash.events.KeyboardEvent;
 public class KeyboardEvents extends Sprite {
  public function KeyboardEvents() {
   init();
  }
  private function init():void {
   stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyboardEvent);
   stage.addEventListener(KeyboardEvent.KEY_UP,onKeyboardEvent);
  }
  public function onKeyboardEvent(event:KeyboardEvent):void {
   trace(event.type);
  }
 }
}

键码

通常人们并不关心一个键是否被按下,而是关心按下的是什么键。使用键盘事件处理有几种方法可以读取输入的信息。前面说到,一个事件处理程序可以由一个事件对象来触发,该对象包括触发这个事件的数据。在键盘事件中有两个相关的属性,事件所涉及的键:字符码(charCode)和键码(keyCode)。
字符码指按下的键所表示的真正字符。例如,用户按下”a”键,字符码就是”a”,如果用户同时又按着 shift 键,这样字符码就是”A”。
键码指按键所代表的数值。如果用户按下”a”键,它所对应的键码为 65,无论是否按着其它键。如果先按下Shift键后按下“a”键,那么会获得两个键盘事件,先是Shift(键码 16)后是 a(键码 65)。 Flash.ui.Keyboard 类同样也有一些属性是针对非字母键的,我们不需要把它们背下来。例如: Keyboard.SHIFT 等于16,当 Shift 键按下后,可以测试其是否等于 Keyboard.SHIFT。请本章的最后一段代码:

package {
 import flash.display.Sprite;
 import flash.events.KeyboardEvent;
 import flash.ui.Keyboard;
 public class KeyCodes extends Sprite {
  private var ball:Sprite;
  public function KeyCodes() {
   init();
  }
  private function init():void {
   ball = new Sprite();
   addChild(ball);
   ball.graphics.beginFill(0xff0000);
   ball.graphics.drawCircle(0, 0, 40);
   ball.graphics.endFill();
   ball.x = stage.stageWidth / 2;
   ball.y = stage.stageHeight / 2;
   stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyboardEvent);
  }
  public function onKeyboardEvent(event:KeyboardEvent):void {
   switch (event.keyCode) {
    case Keyboard.UP :
     ball.y -= 10;
     break;
    case Keyboard.DOWN :
     ball.y += 10;
     break;
    case Keyboard.LEFT :
     ball.x -= 10;
     break;
    case Keyboard.RIGHT :
     ball.x += 10;
     break;
    default :
     break;
   }
  }
 }
}

当我们在 Flash 编辑环境下测试影片时,IDE会拦截用于控制 IDE 自身的键。Tab 键和所有功能键以及作为快捷菜单项的键,在测试影片时不会接收到。不过,我们可以在菜单中选择“控制” -> “禁用快捷键”,来解除限制。这样一来,测试的影片就像在浏览器中工作一样了。

来自:http://www.blueidea.com/tech/multimedia/2008/5701.asp

分类: 交互设计 网页设计 艺术设计



关于酷勤 | 联系方式 | 免责声明 | 友情链接