本文主要解决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;
}
}
}