CocosCreator无尽循环列表,长列表优化drawcall,scrollview列表优化

我这里只实现纵向滑动列表,横向的话直接修改一下就好

cocos creator 2.4.4

参考链接 CocosCreator无尽循环列表,ScrollView优化_zakerhero的博客-CSDN博客

这个链接里实现了横向和纵向的滑动,但是有问题,如果只是实例化文字,那没问题,如果还有图像的刷新,就会出现闪动刷新的效果

原理就是:根据数据量的大小,计算出scrollview的滑动最大高度,时刻监听滑动,根据当前scrollview滑动距离左上角的偏移,和item的单个高度,计算出当前需要实例化的datalist中的index,从index到max最大显示数量,实例化出来,比如max是8个,根据滑动,计算出当前滑动到的开始index是3,就是实例化 index,3到10的实例化,刷新8个数据,接着滑动,下次是4--11,把每个item的位置更新了就可以,然后刷新数据显示

问题:

参考的原文链接 之所以会出现刷新图片闪动的效果,是他时刻都在刷新,更新所有item的显示数据,图片渲染比较慢,就会闪动。

改动:

我这里实现的是滑动后 超出的移除,加到缓存池中,等待下次使用,中间的显示区域不需要刷新,这样就会符合显示了

话不多说直接上代码

// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

const { ccclass, property } = cc._decorator;

@ccclass
export default class main extends cc.Component {

    @property(cc.Node)
    viewContent: cc.Node = null; // 列表挂点

    @property(cc.Node)
    maskNode: cc.Node = null; // 遮罩

    @property(cc.ScrollView)
    scroll: cc.ScrollView = null; // 

    itemPrefab = null; // 预制体
    private startPos = null;// 滑动视图的起始位置
    private cachePool = [];// 移除的等待使用的item池
    private dataList = [];// 数据列表
    private maxNum = 8;// 必须小于服务器返回的每页最大个数,这里每页是20个
    private itemHeight = 105; // 高度+间隙
    private initY = 0;
    private maxY = 0;
    private minY = 0;
    private needSize = 0;//需求要求的高度/宽度
    private visibleHeight = 0;//显示范围高度
    private curOffset = 0;// 滑动偏移 距离左上角
    private miniIdx = 0;// 开始的数据列表index
    private showItemList = [];// 显示的item列表

    private headList = [];// 头像图列表
    async onl oad() {
        this.startPos = cc.v2(this.viewContent.position.x, this.viewContent.position.y);

        this.visibleHeight = this.maskNode.getContentSize().height;
        this.scroll.node.on("scrolling", this.onScrolling.bind(this), this);
        // 加载预制体和图片
        await new Promise((resolve, reject) => {
            cc.resources.loadDir("imgge", cc.SpriteFrame, (err, asset) => {
                if (!err) this.headList = asset;
                resolve(asset);
            })
        })
        await new Promise((resolve, reject) => {
            cc.resources.load("prefab/item", cc.Prefab, (err, asset) => {
                if (!err) this.itemPrefab = asset;
                resolve(asset);
            })
        })
        // 可以请求服务器得到
        // 总共40个数据
        for (let i = 0; i < 40; i++) {
            this.dataList.push(i + 1);
        }
        this.loadList();
    }
    // 每次显示 都初始化一下,用于重新加载数据,滑动重置
    onEnable() {
        this.cachePool = [];
        this.dataList = [];
        this.viewContent.height = 0;
        this.initY = -this.itemHeight / 2;
        this.curOffset = 0;
        this.miniIdx = 0;
        this.showItemList = []

        if (this.startPos) this.viewContent.position = this.startPos; // 重置初始位置
    }
    start() {
    }

    // 加载列表
    loadList() {
        this.viewContent.destroyAllChildren();// 只要是第一页就重新加载;
        this.InitObjs();
        // 设置内容高度
        this.needSize = this.dataList.length * this.itemHeight;
        this.viewContent.setContentSize(new cc.Size(this.viewContent.getContentSize().width, this.needSize));
    }
    // 初始化几个
    InitObjs() {
        let curX = 0;
        let curY = 0;
        for (let i = 0; i < this.maxNum; i++) {
            if (!this.dataList[i]) break;
            let obj = cc.instantiate(this.itemPrefab);
            obj.parent = this.viewContent;
            obj.active = true;
            curY = this.initY - this.itemHeight * i;
            obj.position = cc.v3(curX,curY);
            this.onRefresh(obj,i+"", i);
            this.showItemList.push(obj);
        }
    }
    //计算边界,超过边界则刷新列表
    //offest是左上角原点滑动的偏移量
    private countBorder(offest) {
        let height = this.visibleHeight;//可见高度
        this.minY = offest;//获得相对于左上角原点的最小y值
        this.maxY = offest + height;//获得相对于左上角原点的最大y值
    }
    //强行刷新
    public refresh() {
        let offest = this.curOffset;

        //最大高度,超过该高度,不刷新
        let maxY = this.needSize;
        if (offest < 0 || offest + this.visibleHeight >= maxY)
            return;

        let idx: number = 0;//从0开始
        this.countBorder(offest);
        let lastMinIdx = this.miniIdx;
        let miniIdx = Math.floor(offest / this.itemHeight);
        // 当每次更新miniIdx 不同的时候,就是移除和新建的时候
        if (this.miniIdx != miniIdx) {
            let curY = this.initY - this.itemHeight * miniIdx;// 当前要开始的y,大于y的删除
            let curEndY = this.initY - this.itemHeight * (miniIdx + this.maxNum);// 当前要结束的y,小于y的删除
            let deleteNodeUuIdList = [];// 需要移除的uuid 
            let remainList = [];// 剩余的data index
            this.showItemList.forEach((item, index) => {
                if (item.position.y > curY || item.position.y <= curEndY) {// 大于当前展示的坐标或者小于当前展示的最小坐标,就可以移除
                    deleteNodeUuIdList.push(item.uuid);
                }
                else {
                    remainList.push(lastMinIdx + index); // 这里的顺序 iten列表对应上次实例化的data的index
                }
            })
            let len = this.showItemList.length;
            for (let index = len - 1; index >= 0; index--)// 逆序移除 防止移除多个问题
            {
                let item = this.showItemList[index];
                if (deleteNodeUuIdList.indexOf(item.uuid) >= 0)// 在删除列表里,就删除,加到缓存列表中
                {
                    this.cachePool.push(item);
                    this.showItemList.splice(index, 1);
                }
            }
            this.miniIdx = miniIdx; // 更新
            for (let i = 0; i < this.maxNum; i++) {
                idx = this.miniIdx + i;
                if (remainList.indexOf(idx) < 0)// 没有包含的 新实例化的数据,在剩余item中没有的就创建
                {
                    this.refreshItem(idx, i);
                }
            }
        }
    }

    //idx是UI该刷新的第几个元素
    private refreshItem(idx, objIdx) {
        if (idx < 0 || idx >= this.dataList.length)
            return;
        let obj = this.cachePool.pop();
        if (obj == null) {
            console.error("obj is null!");
            return;
        }

        let curX = 0;
        let curY = 0;
        curY = this.initY - this.itemHeight * idx;

        // console.error("idx:" + idx + ",curX:" + curX + ",curY:" + curY);
        obj.position = cc.v3(curX, curY);
        obj.active = true;
        this.onRefresh(obj, objIdx, idx);

        this.showItemList.push(obj);
        // 这里坐标按照显示从上到下  从大到小的排序 ,因为this.showItemList的都是后面push ,实例化下面数据,可以push,但是实例化上面数据,需要加到最前面,按照位置,
        // 否则删除的时候根据index  remainList是根据index+lastMinIdx保留的,也就是数据列表的index,实例化上面数据index push到最后,到时候remian中index和实际的数据index不对应
        this.showItemList.sort((a, b) => {
            return -a.position.y + b.position.y;
        })
    }

    /**
     * 刷新回调
     * @param obj 
     * @param idx 需求显示的索引
     * @param objIdx 实际的item索引
     */
    private onRefresh(obj, idx: string, objIdx) {
        let head = obj.getChildByName("head").getComponent(cc.Sprite);
        let num = obj.getChildByName("num").getComponent(cc.Label);
        head.spriteFrame = this.headList[objIdx];
        num.string = this.dataList[objIdx];
    }

    // 滑动中
    onScrolling() {
        //获取滚动视图相对于左上角原点的当前滚动偏移
        let scrollOffset: cc.Vec2 = this.scroll.getScrollOffset();
        this.curOffset = scrollOffset.y;
        this.refresh();
    }
    update(dt) {
    }
}

效果图 我这里把mask去掉了,加了一个蒙版,方便看到效果,蒙版区域是可滑动区域

CocosCreator无尽循环列表,长列表优化drawcall,scrollview列表优化

 项目 github 地址 https://github.com/SuiFengErQu/scrollview-Long-list-optimization-drawcall

上一篇:Codeforces Round #754 (Div. 2) 部分题解


下一篇:Codeforces Round #753 (Div. 3) (A~E)