AS3中的DisplayObject有一个render事件,他会在重绘DisplayList之前触发,这给我们提供了在重绘之前最后一次操作的机会。
每次需要DisplayObject触发render事时,都要调用一次 stage.invalidate();
下面用一个小例子来说明一下具体用法把。
假设我们现在要写一个list组件,该组件有addItem()方法用于添加list项目,和remvoeItem() 方法用于删除list项目,当然还可能有addItemAt(),removeItemAt()等方法,这些方法调用后,都需要对list内的显示对象进 行重新排列。
我们先实现一个List类,用于显示列表项目
List类中,有addItem() 和 removeItem() 这两个方法提供给外部调用,用于添加和删除list项目,这两个方法中除了将列表项目添加/删除,还要调用一个方法来重新对list中的项目进行排列,layoutContents()。
关键就是,这个layoutContents()的调用,他的调用次数越少,那效率当然就越高啦,如果是常规的做法,就是类似这样:
addChild(item);
layoutContents();
}
将item加入后,重新排列列表
下面是List类的源代码:
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
public class List extends Sprite
{
public function addItem(item:DisplayObject):void
{
addChild(item);
layoutContents();
}
public function removeItem(item:DisplayObject):void
{
if (contains(item))
{
removeChild(item);
layoutContents();
}
}
//对内部项目进行排列,可以是任意的排列算法
protected function layoutcontents():void
{
trace("do layout");
var y:Number = 0;
var num:int = numChildren;
for (var i:int = 0; i < num; i++)
{
var child:DisplayObject = getChildAt(i);
child.x = 0;
child.y = y;
y += child.height + 2;
}
}
}
}
这个程序粗看似乎没什么问题,但却存在一个效率问题
如果只调用一次addItem,没问题,如果调用10次呢? 前9次的layoutcontents()都不是必须的,只有第十次才是真正需要的这样程序的效率就降低了。
我们可以试一下
先需要一个简单的ListItem
{
import flash.display.Shape;
public class ListItem extends Shape
{
public function ListItem()
{
super();
graphics.beginFill(0xFF6600);
graphics.drawRect(0, 0, 30, 16);
graphics.endFill();
}
}
}
然后测试
{
import flash.display.Sprite;
public class ListTest extends Sprite
{
public function ListTest()
{
var list:List = new List();
addChild(list);
list.addItem(new ListItem());
list.addItem(new ListItem());
list.addItem(new ListItem());
}
}
}
我们可以看到,输出了3次 do layout 说明layoutcontents执行了3次,前两次都是多余的。
现在,解决办法就是利用render事件啦。
因为在当前帧内,显示列表更新前会触发render事件,所以在render事件触发后来排列列表项目,就可以保证排列方法在做了任意次的添加或删除操作后只需调用一次,从而提高效率。
这么做只需要对List类稍做一些改动,首先肯定是要监听render事件,我们可以仅监听stage对象的render事件即可,因为这样以后可以做一个独立的RepaintManger来管理所有组件的重绘(可以参考AsWing的RepaintManager类)。
在render事件触发后,做我们需要的调整,由于要render事件触发,就必须先调用stage.invalidate() ,所以每次添加或删除list项目后,都要执行一次该方法,即:
{
//
stage.invalidate()
}
由于是监听的stage的render事件,所以在添加删除操作后,要做一个标记,表示list有改动,需要在render事件后重新排列,如果该标记为 false,那么即使render触发了也不做排列,因为stage的render事件也有可能是由于该stage内的其他child需要重绘而造成 stage的render触发。
下面是改过后的List代码
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.events.Event;
public class List extends Sprite
{
private var changed:Boolean;
public function List()
{
super();
addEventListener(Event.ADDED_TO_STAGE, __addToStage);
}
public function addItem(item:DisplayObject):void
{
addChild(item);
requireLayout();
}
public function removeItem(item:DisplayObject):void
{
if (contains(item))
{
removeChild(item);
requireLayout();
}
}
private function requireLayout():void
{
changed = true;
if (stage != null)
stage.invalidate();
}
//对内部项目进行排列,可以是任意的排列算法
protected function layoutContents():void
{
trace("do layout");
var y:Number = 0;
var num:int = numChildren;
for (var i:int = 0; i < num; i++)
{
var child:DisplayObject = getChildAt(i);
child.x = 0;
child.y = y;
y += child.height + 2;
}
}
private function __addToStage(e:Event):void
{
stage.addEventListener(Event.RENDER, __render);
if (changed)
stage.invalidate();
}
private function __render(e:Event):void
{
if (changed)
{
layoutContents();
changed = false;
}
}
}
}
当我们再次运行ListTest的时候,do layout 只输出了一次。
就是这些内容,当然,你可能会说,需要做到这些根本不需要这么复杂,只要公开layoutContents方法,在所有操作调用之后让调用者自行调用一次layoutContents()。
在 这个例子中当然可以,但是当情况很复杂的时候,使用者每进行一次操作都要自行调用更新的方法,这样做并不是好的解决方案。试想,如果 flashplayer不会为我们处理显示DisplayObject的工作,而是每次addChild/removeChild之后,我们都需要自行调 用flashplayer底层的方法来让我们需要的东西显示出来,这样做显然很不好。