Egret引擎List虚拟布局竟然可以这么用

1,494 阅读2分钟

本文主要解决egret引擎List虚拟布局不同高度Item对象重用时导致的布局异常问题

功能需求:聊天界面,里面有文字聊天,红包等类型的消息,不同类型的Item高度是不一样的,需要通过对象重用来优化性能

问题描述:对象重用,很自然想到 开启eui.List的虚拟布局,设置useVirtualLayout=true,但是如果itemRenderer的height高度会变化的时候,egret引擎并不支持,运行时会出现item重叠等布局异常的问题

解决办法:自己重写一个简化版的list,scroller时对象重用和显示

核心代码

    class GameList extends eui.Group{
        public useVirtualLayout:boolean;

        public itemRenderer;

        private _itemRendererFunction:Function;
        public set itemRendererFunction(itemFun:any){
            this._itemRendererFunction = itemFun;
        }
        public get itemRendererFunction(){
            return this._itemRendererFunction;
        }

        private scroller:eui.Scroller;

        private desTop = 0;
        private margin = 16;
        private marginLeft = 0;
        private marginBottom = 0;

        private dataArr;
        private pool = {};
        private itemArr = [];

        private scrollTime;
        public constructor() {
            super();
        }

        public childrenCreated() {

            this.scroller = <eui.Scroller>this.parent;
            this.scroller = <eui.Scroller>this.parent.parent;

            this.scroller.addEventListener(egret.Event.CHANGE,this.onScrollEvent,this)
            this.scroller.addEventListener(eui.UIEvent.CHANGE_END,this.onScrollEndEvent,this)
        }

        private resetScrollV(scroller){

        }

        public set dataProvider(dp: eui.ArrayCollection){
            this.scroller.stopAnimation();//先停止滚动中的list

            // console.log("dataprovider")
            this.clean();
            this.setData(dp);

            var _dp = (<eui.ArrayCollection>dp);
            var type = eui.CollectionEvent.COLLECTION_CHANGE;
            if(!_dp.hasEventListener(type)) _dp.addEventListener(type, ()=>{
                if(!this.stage) return;
                // console.log("dataprovider2")

                this.setData(dp);

            }, this);
        }

        private setData(dp){

            this.dataArr = dp.source;
                
            for(var i=0;i<this.itemArr.length;i++){
                if(this.dataArr.indexOf(this.itemArr[i].data) == -1){
                    this.feeItem(this.itemArr[i]);
                    this.itemArr.splice(i,1);
                    i--;
                }
            }
            // this.heightObj = {};
            // this.height = this.scroller.height;
            this.resetHeight();//重设列表高度

            this.once(egret.Event.ENTER_FRAME, this.refushData, this)
        }

        private refushData(){
            this.renew();
        }

        private onScrollEvent(e?){
            // console.log("onScrollEvent",e)
            if(!this.dataArr)
                return;
            this.resetScrollV(this.scroller)
            this.onScroll(this.scrollV)
            this.scrollTime = egret.getTimer();

        }
        private onScrollEndEvent(){
            if(!this.dataArr)
                return;
            if(egret.getTimer() - this.scrollTime < 500)
                this.onScrollEvent();
        }

        private getItem(data):any{
            if(this.itemRendererFunction){
                this.itemRenderer = this.itemRendererFunction(data);
            }
            let classname = this.itemRenderer.classname;
            if(!this.pool[classname]){
                this.pool[classname] = [];
            }
            var item = this.pool[classname].pop();
            if(!item)
            {
                item = new this.itemRenderer();
                item.classname = classname;
                item['x'] = this.marginLeft || 0
            }
            return item;
        }

        private feeItem(item){
            this.pool[item.classname].push(item)
            if(item.parent)
                item.parent.removeChild(item)
        }

        private renew(){
            // console.log("renew", this.scrollV)
            this.onScroll(this.scrollV)
        }

        public get scrollV(){
            return this.scroller.viewport.scrollV;
        }

        public set scrollV(v:number){
            // console.log("set scrollV", v);
            if(v < 0) v = 0;
            this.scroller.viewport.scrollV = v;
            // this.onScrollEvent();
            this.once(egret.Event.ENTER_FRAME, this.onScrollEvent, this)
        }

        public validateNow(){

        }

        public clean(){
            while(this.itemArr.length > 0)
                this.feeItem(this.itemArr.pop());
        }

        public onScroll(v){
            var vh = this.scroller.height;
            var change = false;
            var hcount = v - 200;
            var endY = v + vh;

            //将过小的清掉、将超过的清除
            for(var i=0;i<this.itemArr.length;i++)
            {
                if(this.itemArr[i].y < hcount || this.itemArr[i].data.yyy < hcount
                    || this.itemArr[i].y > endY || this.itemArr[i].data.yyy > endY)
                {
                    this.feeItem(this.itemArr[i]);
                    this.itemArr.splice(i,1);
                    i--;
                    change = true;
                }
            }
            
            var showItem;
            for(var i=0, len = this.dataArr.length; i<len; i++)  {
                let y = this.dataArr[i].yyy;
                if(y >= hcount){
                    if(y > endY)
                        break;
                    showItem = this.isItemShow(this.dataArr[i]);
                    if(!showItem)
                    {
                        var item = this.getItem(this.dataArr[i]);
                        this.itemArr.push(item);
                        this.addChild(item);
                        item.data = this.dataArr[i];
                        item.y = y;
                    }
                    else
                        showItem.y = y;
                }
            }
        }

        private isItemShow(data){
            for(var i=0;i<this.itemArr.length;i++)
            {
                if(this.itemArr[i].data == data)
                {
                    return this.itemArr[i];
                }
            }
            return null;
        }

        private getHeight(data){
            if(data._HEIGHT) 
                return data._HEIGHT + this.margin;
            var item = this.getItem(data);
            if(this.itemRenderer.getHEIGHT){
                data._HEIGHT = this.itemRenderer.getHEIGHT(data);
                this.feeItem(item);
                return data._HEIGHT + this.margin;
            }
            if(this.itemRenderer.HEIGHT){
                this.feeItem(item);
                return this.itemRenderer.HEIGHT + this.margin;
            }

            this.addChild(item);
            item.data = data;
            item.validateNow();
            var h = item.height + this.margin;
            this.feeItem(item);
            this.itemRenderer.HEIGHT = item.height;
            return h;
        }

        private resetHeight(){
            //this.contentHeight = 0;
            var contentHeight = 0;
            for(var i=0, len = this.dataArr.length; i<len; i++)  {
                this.dataArr[i].yyy = contentHeight;
                contentHeight += this.getHeight(this.dataArr[i]);
            }
            contentHeight += this.desTop + this.marginBottom - this.margin;
            this.height = contentHeight;
        }

        public get contentHeight(){
            return this.height;
        }

        public renewList(){
            for(var i=0;i<this.itemArr.length;i++)
            {
                this.itemArr[i].dataChanged();
            }
        }
      
    }

用法,和eui.List一致,需要注意的是 itemRenderer 需要提供一个classname属性和getHEIGHT静态方法,参考代码如下:

let list = new GameList();
list.itemRenderer = ChatItem;
list.dataProvider = new eui.ArrayCollection([]);

class ChatItem extends game.BaseItem {
	public static classname = "ChatItem";

	constructor(){
		super();
		this.skinName="ChatItemSkin";
	}

	protected childrenCreated(): void {
		super.childrenCreated();
		//待补充逻辑
	}

	public dataChanged(){
		//待补充逻辑
	}

	public static getHEIGHT(data){

		var data = data.vo;
		if(!data){
			return 45;
		}
		else if(data.redresultopenid){
			return 24;
		}
		else if(data.msgType == 2){
			return 69;
		}
	}
}