CocosCreator开发笔记(5)-ScrollView之动态更新的优化原理

ScrollView是比较常用的UI组件之一,游戏中的任务榜、排行榜都少不了它,实际使用中存在一个问题,例如:在排行榜中要显示前100名玩家,如果真的把这100名玩家的信息全部创建,并加载进ScrollView,对移动设备的宝贵内存会是巨大的浪费。其实玩家在屏幕上总能看到的最多只有7、8项而已,所以实际上只用创建比显示多一点的数量,再通过缓冲区实时动态更新数据,就可以给玩家呈现出尽可能多数量的列表(依内存而定,理论上无限),即节省内存,也不影响性能。
在CocosCreate官方提供的example中,有个ScrollView的例子,就使用了动态更新数据,我把它摘出来做为一个单独的工程,加上中文注释,并对代码稍做改动,运行截图如下所示:
CocosCreator开发笔记(5)-ScrollView之动态更新的优化原理_第1张图片
  可以看到,列表中显示一共有100行,但一屏最多展示7.5行,而实际上在内存中只真正创建了15项,所有看到的这100行,都是在上下滚动事件中,通过动态更新这15项的坐标和内容来实现的。要了解它的运行原理,先看下图:
CocosCreator开发笔记(5)-ScrollView之动态更新的优化原理_第2张图片
  如图把ScrollView分成三部分,按区域从小到大依次是:
1、屏幕可见区。指屏幕上玩家可看可操作的列表区域,在此demo中有7.5行;
2、缓冲区。指内存中真正创建了的列表所占的区域,在此demo中有15行;
3、content区。指整个ScrollView要显示的区域,在此demo中有100行;

下面再分三种情况讲解:
1、**刚初始化完成时:**此时在右侧按钮上提示有100行,实际上只创建了第1-15行,而玩家能看到的是第1-7行。如果玩家想要看到更多,必然会向上或向下滚动屏幕;
2、**向上滚动时:**在移动设备上,如果玩家想要看到下面的行,所做的操作是触摸往上滑动,则整个content区往上移动,也带动content区的item往上移动,update函数会不断遍历所创建的15项item,如果检测到某item的y坐标超出了缓冲区的上边界(该item已经被玩家看过或不想再看),则把该item往下移动一个缓冲区的高度(移动该item到玩家即将看到的位置),并更新它的显示ID;
3、**向下滚动时:**同理,content区的item往下移动,update不断遍历所创建的15项item,如果检测到某item的y坐标越过了缓冲区的下边界(该item已经被玩家看过或不想再看),则把该item往上移动一个缓冲区的高度(移动该item到玩家即将看到的位置),并更新它的显示ID;
  关键代码如下所示:

// 返回item在ScrollView空间的坐标值
    getPositionInView: function (item) {
        let worldPos = item.parent.convertToWorldSpaceAR(item.position);
        let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
        return viewPos;
    },

    // 每帧调用一次。根据滚动位置动态更新item的坐标和显示(所以spawnCount可以比totalCount少很多)
    update: function(dt) {
        this.updateTimer += dt;
        if (this.updateTimer < this.updateInterval) {
            return; // we don't need to do the math every frame
        }
        this.updateTimer = 0;
        let items = this.items;
        // 如果当前content的y坐标小于上次记录值,则代表往下滚动,否则往上。
        let isDown = this.scrollView.content.y < this.lastContentPosY;
        // 实际创建项占了多高(即它们的高度累加)
        let offset = (this.itemTemplate.height + this.spacing) * items.length;
        let newY = 0;

        // 遍历数组,更新item的位置和显示
        for (let i = 0; i < items.length; ++i) {
            let viewPos = this.getPositionInView(items[i]);
            if (isDown) {
                // 提前计算出该item的新的y坐标
                newY = items[i].y + offset;
                // 如果往下滚动时item已经超出缓冲矩形,且newY未超出content上边界,
                // 则更新item的坐标(即上移了一个offset的位置),同时更新item的显示内容
                if (viewPos.y < -this.bufferZone && newY < 0) {
                    items[i].setPositionY(newY);
                    let item = items[i].getComponent('Item');
                    let itemId = item.itemID - items.length; // update item id
                    item.updateItem(i, itemId);
                }
            } else {
                // 提前计算出该item的新的y坐标
                newY = items[i].y - offset;
                // 如果往上滚动时item已经超出缓冲矩形,且newY未超出content下边界,
                // 则更新item的坐标(即下移了一个offset的位置),同时更新item的显示内容
                if (viewPos.y > this.bufferZone && newY > -this.content.height) {
                    items[i].setPositionY(newY);
                    let item = items[i].getComponent('Item');
                    let itemId = item.itemID + items.length;
                    item.updateItem(i, itemId);
                }
            }
        }

        // 更新lastContentPosY和总项数显示
        this.lastContentPosY = this.scrollView.content.y;
        this.lblTotalItems.string = "Total Items: " + this.totalCount;
},

在此demo中,ScrollView列表显示的item其实是个按钮,而它是做为预制资源,其实可以在Creator中编辑成各种UI,并不局限于按钮形式。

你可能感兴趣的:(Cocos)