利用stage.invalidate()方法和render事件提高as3程序的運行效率

AS3中的DisplayObject有一個render事件,他會在重繪DisplayList之前觸發,這給我們提供了在重繪之前最後一次操作的機會。
每次需要DisplayObject觸發render事時,都要調用一次 stage.invalidate();

下面用一個小例子來說明一下具體用法把。
假設我們現在要寫一個list組件,該組件有addItem()方法用於添加list項目,和remvoeItem() 方法用於刪除list項目,當然還可能有addItemAt(),removeItemAt()等方法,這些方法調用後,都需要對list內的顯示對象進行重新排列。
我們先實現一個List類,用於顯示列表項目
List類中,有addItem() 和 removeItem() 這兩個方法提供給外部調用,用於添加和刪除list項目,這兩個方法中除了將列表項目添加/刪除,還要調用一個方法來重新對list中的項目進行排列,layoutContents()
關鍵就是,這個layoutContents()的調用,他的調用次數越少,那效率當然就越高啦,如果是常規的做法,就是類似這樣:

代碼:

public function addItem(item:DisplayObject):void
{
    addChild(item);
    layoutContents();
}

將item加入後,重新排列列表

下面是List類的源代碼:

代碼:

package {
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();
    }
}

這個程序粗看似乎沒什麼問題,但卻存在一個效率問題
如果只調用一次addItem,沒問題,如果調用10次呢? 前9次的layoutcontents()都不是必須的,只有第十次纔是真正需要的這樣程序的效率就降低了。
我們可以試一下

先需要一個簡單的ListItem

代碼:
package
{
import flash.display.Shape;
public class ListItem extends Shape
{
public function ListItem()
{
    super();
    graphics.beginFill(0xFF6600);
    graphics.drawRect(0, 0, 30, 16);
    graphics.endFill();
}
}
}

然後測試
package {
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項目後,都要執行一次該方法,即:
代碼:
public function addItem(item:DisplayObject):voide {
......
stage.invalidate()
}

由於是監聽的stage的render事件,所以在添加刪除操作後,要做一個標記,表示list有改動,需要在render事件後重新排列,如果該標記爲 false,那麼即使render觸發了也不做排列,因爲stage的render事件也有可能是由於該stage內的其他child需要重繪而造成 stage的render觸發。


下面是改過後的List代碼
package {
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; inum; 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底層的方法來讓我們需要的東西顯示出來,這樣做顯然很不好。
完了,就這些東東,寫完之後俺感覺自己的表達能力不好,如果覺得我說的很模糊,那就研究下代碼吧,全部代碼都在上面了,歡迎指教和討論 ^^

 

from: http://www.eoocy.com/article.asp?id=8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章