23.4. Event.RENDER 事件的优化
Event.RENDER 事件是一个在需要画质优先的情况下使用的特殊类型的屏幕更新事件。它的根本目的是让程序员推迟所有自定义绘图任务直到一个屏幕被渲染前的某个精确的时刻,这消除了重复的绘制行为。不像其他内置Flassh运行时事件, Event.RENDER 事件必须通过程序员手动请求。当下面2个条件都满足的时候,Flash运行时发送 Event.RENDER事件:
• Flash运行时准备检查屏幕是否需要更新 (走帧的时候或者是调用updateAfterEvent() 的时候)
• 程序员调用了stage.invalidate() (stage.invalidate()是程序员请求Flash运行时在下一次屏幕更新检查发生的时候派送Event.RENDER 事件的手段)
我们来假设一个例子,展示Event.RENDER 事件如何被用来提高画质。假设我们创建了一个Ellipse类代表一个屏幕上的椭圆shape。为了简便,假设这个椭圆为白色,并有1像素宽的黑边。我们的
Ellipse 类有2个责任:
• 他必须管理一个椭圆概念上的数据 (比如.,储存椭圆的宽和高)
• 他必须绘制一个基于概念上的椭圆的一个屏幕上的椭圆,并当椭圆概念变化时在屏幕上重绘这个椭圆
Example 23-2 被赋予了这些责任,他展示了我们如何下不关心画质时创建这个Ellipse 类。
Example 23-2.一个简单的椭圆类
package {
import flash.display.Shape;
public class Ellipse extends Shape {
private var w:Number;
private var h:Number;
public function Ellipse (width:Number,height:Number) {
w = width;
h = height;
draw();
}
public function setWidth(newWidth:Number):void {
w = newWidth;
draw();
}
public function getWidth ():Number {
return w;
}
public function setHeight(newHeight:Number):void {
h = newHeight;
draw();
}
public function getHeight ():Number {
return h;
}
private function draw ():void {
graphics.lineStyle(1);
graphics.beginFill(0xFFFFFF, 1);
graphics.drawEllipse(0,0, w, h);
}
}
}
注意在Ellipse 类中概念椭圆在3处地方改变了: setWidth()方法, setHeight() 方法,以及Ellipse 构造方法。为了维持概念椭圆和屏幕上的椭圆一致,我们必须保证屏幕上的椭圆要在这3处地方被重绘。
Example 23-2 中的代码提供了实现这个一致要求的强力方法;他仅仅是在每次getWidth(), getHeight(), 或者Ellipse构造方法执行的时候调用了draw()。当然,如果这些函数在已经一致的情况下被调用,那么draw()的调用就是多余的了。下面是代码:
var e:Ellipse = new Ellipse (100, 200); // draw() invoked here
e.setWidth(25); // draw() invoked again here
e.setHeight(50); // draw() invoked again here
当上面3行代码运行, draw() 方法会被调用3次,但是屏幕只会显示最后一次调用的结果。前2步调用是多余且浪费的。在一个简单的程序里,这个冗余可能无法察觉从而被容忍。但是,在一个复杂的程序中,一个微小的冗余就可能会造成绘图活动浪费地执行成百上千次,可能会导致严重的画面表现问题。
要在Ellipse中移除冗余,我们必须改变它的绘图策略。不是在每次概念椭圆改变的时候调用draw(),而是将draw()推迟到屏幕更新。这个新的策略将使得Ellipse类更加复杂,但是会提高表现能力。
实施这个新的"单一draw()" 策略的第1步是从etWidth(), setHeight(),以及构造函数中移除draw()方法。作为直接执行draw() 方法的代替, 这些函数将会调用stage.invalidate(), 它可以强制Flash运行时在下一次进行屏幕更新检查的时候派送Event.RENDER事件。然后,在这个Event.RENDER 侦听器函数中我们调用draw(). Example 23-3 显示了改进的Ellipse类, 与 Example23-2不同的地方标注了黑体 。注意当Ellipse 对象不在显示列表时,不应当调用draw() ,所以只有当Ellipse对象在显示列表时stage.invalidate()才可以被调用。要判断Ellipse 对象是否在显示列表中,我们可以检查这个对象的继承的实例属性stage. 若 stage == null, Ellipse对象就不在显示列表中。
NOTE
一个请求Event.RENDER 事件通知的对象即使该对象不在显示列表中,它仍然会接收到通知。
注意在这个临时舞台上,Ellipse 类不是当前功能的因为他还没有注册Event.RENDER事件;我们将很快处理这个任务。
Example 23-3. 改进的Ellipse 类, part 1
package {
import flash.display.Shape;
public class EllipseInterim extends Shape{
private var w:Number;
private var h:Number;
public function EllipseInterim(width:Number, height:Number) {
w = width;
h = height;
// 如果该对象不在显示列表中...
if (stage != null) {
// ...请求Event.RENDER 派送
stage.invalidate();
}
}
public function setWidth(newWidth:Number):void {
w = newWidth;
if (stage != null) {
stage.invalidate();
}
}
public function getWidth ():Number {
return w;
}
public function setHeight(newHeight:Number):void {
h = newHeight;
if (stage != null) {
stage.invalidate();
}
}
public function getHeight ():Number {
return h;
}
// 当屏幕准备进行更新的时候事件侦听器被调用,stage.invalidate()被调用
private function renderListener(e:Event):void {
draw();
}
private function draw ():void {
graphics.clear();
graphics.lineStyle(1);
graphics.beginFill(0xFFFFFF, 1);
graphics.drawEllipse(0, 0, w, h);
}
}
}
要使得 renderListener()在Flash运行时派送Event.RENDER时执行,我们必须为Stage实例注册renderListener() 以侦听 Event.RENDER事件。但是,当一个Ellipse 对象不在显示列表中时,它的实例属性stage 为null,从而无法被用做注册事件。要避免这个问题,Ellipse类必须定义一个事件侦听器函数—addedToStageListener() 和 removedFromStageListener()—他们将侦听自定义事件StageDetector.ADDED_TO_STAGE和 StageDetector.REMOVED_FROM_STAGE.
StageDetector.ADDED_TO_STAGE 事件将会在一个对象被添加到显示列表时派送,在这个时候
Ellipse 类将注册 renderListener() 以侦听Event.RENDER 事件.StageDetector.REMOVED_FROM_STAGE 事件将会在一个对象从显示列表中移除的时候派送,这个时候, Ellipse 类就注销 renderListener() 对 Event.RENDER事件的侦听.
Example 23-4 展示了这个改进的Ellipse类。注意addedToStageListener() 方法调用stage.invalidate(), 以确保任何对还不在显示列表Ellipse对象的改动都会在它被添加到显示列表时被渲染。
NOTE
Example 23-4 依赖于自定义事件StageDetector.ADDED_TO_STAGE和StageDetector.REMOVED_FROM_STAGE, 他们派送自自定义类StageDetector.要看完整的StageDetector类,在"The ADDED_TO_STAGE andREMOVED_FROM_STAGE Events" in Chapter 20.
Example 23-4. 改进的 Ellipse 类, part 2
package {
import flash.display.Shape;
import flash.events.*;
public class EllipseInterim extends Shape{
private var w:Number;
private var h:Number;
public function EllipseInterim(width:Number, height:Number) {
// 注册以便当对象从显示列表中添加或移除时收到通知
var stageDetector:StageDetector = newStageDetector(this);
stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,addedToStageListener);
stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE, removedFromStageListener);
w = width;
h = height;
if (stage != null) {
stage.invalidate();
}
}
public function setWidth(newWidth:Number):void {
w = newWidth;
if (stage != null) {
stage.invalidate();
}
}
public function getWidth ():Number {
return w;
}
public function setHeight(newHeight:Number):void {
h = newHeight;
if (stage != null) {
stage.invalidate();
}
}
public function getHeight ():Number {
return h;
}
// 当shape被添加到显示列表时调用事件侦听器
private function addedToStageListener(e:Event):void {
// 注册以接收屏幕更新的通知
stage.addEventListener(Event.RENDER,renderListener);
// 确保该对象不在屏幕上时发生的改动会在添加到显示列表时被渲染
stage.invalidate();
}
// 当shape从显示列表移除时调用事件侦听器
private function removedFromStageListener(e:Event):void {
// 当该对象不在屏幕上时不用接收屏幕更新事件
stage.addEventListener(Event.RENDER, renderListener);
}
private function renderListener(e:Event):void {
draw();
}
private function draw ():void {
graphics.clear();
graphics.lineStyle(1);
graphics.beginFill(0xFFFFFF, 1);
graphics.drawEllipse(0, 0, w, h);
}
}
}
在前面的Example 23-4 中展示的Ellipse 类现在已经具备完整的功能,但是仍然承受着巨大的冗余。首先,addedToStageListener() 方法经常调用stage.invalidate(),无论一个Ellipse 对象是否在显示列表。这导致即使概念椭圆不在屏幕上的时候没有被改变但仍然进行了不必要的重绘。第二,要记住Event.RENDER事件会在任何对象(不仅仅是当前对象)调用stage.invalidate() 时发生。因此,在他的当前状态,renderListener() 将会在该程序的任何对象调用调用stage.invalidate().的时候调用draw() 。在一个用于许多对象的程序中这会造成严重的画面表现问题.
要处理这最后的2个问题,我们将对Ellipse 类进行最后的设置,添加一个新的逻辑来追踪当addedToStageListener() 和renderListener()被调用时这个椭圆是否需要重绘。首先我们添加一个新的实例属性, changed, 指示了Ellipse 是否需要在下一次屏幕更新时被重绘。然后,为了设置和解除changed, 以及检查它的状态,我们将添加3个方法:setChanged(), clearChanged(), 和 hasChanged(). 最后,我们在椭圆被改变时(比如setWidth(), setHeight(), 或者构造方法被调用时)设置 changed 为 true 。Example 23-5 展示了这最终的Ellipse类, 包含了被改动的黑体部分以及指导性的注释。正如之前提到的,Example 23-5的这个类比Example 23-2中原始的类要更复杂但是在一个需要画质优先的程序中,这种渲染延迟的手法是不可或缺的。更多使用渲染延迟的shape类的广泛库,请看Chapter25.
Example 23-5. 最终、最佳的椭圆类
![(2012-01-08 老物搬运)[EssentialActionScript3.0中文版]无责任翻译-23章屏幕更新(3)_第1张图片](http://img.e-com-net.com/image/info5/89ea791f215c4319b85a999de10ea8b3.gif)
23.5. Let's Make It Move!
现在我们已经熟悉了Flash运行时的屏幕更新系统,我们已经准备好依靠它来学习编写动画和动作代码。有趣吗?请看下一章。