COCOS CREATOR的scroll view性能优化记录

  • Post author:
  • Post category:其他


面试题: cocos2d-x中 ScrollView,TableView,ListView区别

TableView,ListView都继承自ScrollView

用法区别:

少量数据使用ListView,ScrollView比较好,大量的数据插入及其访问,则用TableView好

为什么?

because:

ListView,ScrollView每次都是加载的所有的item,所有数据量越大,卡顿越是明显

而TableView则是加载的你所看到的几条item,所以即使在多的数据,则只有几条,

所以大量的数据加载,则使用TableView

少量数据则使用ListView,ScrollView

————————————————————————————————————————————————————

Listview很多人都有使用过,当加载大量数据的时候会出现卡顿也是经常有的事情,最主要的问题在于2个地方,

1 一次性创建过多的item 内存分配、ui布局等底层代码的消耗(这个不算最主要)

2 貌似listview的pushback…之类的方法会调用widget的clone十分消耗效率(这个重要…我也是从网上看来的…没看listview的源码)

我们需要的效果是秒加载 无卡顿这个是最主要的目的,其次的目的 如果加载过多的item肯定会造成内存飙升,我记得看到文章说有兄弟踩坑的时候加载大量item导致直接闪退…我在网上看了一部分文章觉得比较好的处理办法就是在scrollview的可视(剪裁区域内)加载能铺满整个区域的item,然后在多增加10个item 也就是说你需要加载的item的数量是scrollview.list.height / (item.heigh + spacingY) + 10 这个数量的item 一般来说也就10多个item的样子 ,整个scrollview只需要用到这么多的item 保证了内存的开销很低并且加载速度会非常的快.接下来的思路就是计算一个顶部最大Y坐标和底部最大Y坐标。

如图:

在这里插入图片描述

红色区域为scrollview的可视剪裁区,上部黑框和下部黑框是用来做缓冲的,假设可视区域内部一共只需要3个item即可填充满,那么我们需要的总共item数量是13个,剩下的10个是预留给上部黑框和下部黑框做缓冲的item, 也就是说当我们滑动这个scrollview的时候实际上是在移动scrollview内部的list这个node.我们可以在update中去循环检测每一个节点的Y坐标 ,当然 我们需要先计算出这个最大Y和最小Y坐标,一旦发现某个item的Y坐标超出了最大Y或者最小Y的时候,如果是往上滑动则立即将这个item的内容重新刷新,然后将item移动到所有节点的最尾端,比如当前的item是0-13,一旦当item0的Y坐标大于最大Y的时候我们立马刷新该item的坐标到最底部,然后将item的内容刷新为第14个item的内容,反方向滑动同理解决. 这样我们即可实现在极少量的item数量下实现大量的item显示效果。既保证加载效率又保证内存开销低。下面是代码 我这个做法呢 写demo的时候并没有考虑多行多列的情况,仅仅是考虑了列表模式,用来做一些排行榜,任务列表是足够使用的。

scrollview.js

cc.Class({
    extends: cc.Component,
 
    properties: {
        //scrollview的view剪裁节点
        view:cc.Node,
        //scrollview的具体装载item的node
        list:cc.Node,
        //item的prefab
        itemPrefab:cc.Prefab,
        //item的间隔
        spacingY:0,
    },
 
    // LIFE-CYCLE CALLBACKS:
    
    onLoad () {
        //预加载的item的数据
        this.data = []
        //当前可视区域内部填充满需要的item数量
        this.rowItemCounts = 0 
        //创建的item节点的数组
        this.items = []
        //顶部最大Y
        this.topMax = 0
        //底部最小Y
        this.bottomMax = 0
        //上一次listnode的Y坐标
        this.lastListY = 0
        //itemprefab的高度
        this.itemHeight = 0
    },

    //使用需调用init方法传递进data数组 
    init(data){
        this.data = data 
        //保存高度
        let height = 0
        //创建item 
        let item = cc.instantiate(this.itemPrefab)
        height = item.height
        this.itemHeight = height
        //计算可视区域内部填充满需要的item数量
        this.rowItemCounts = Math.ceil(this.view.height / (height + this.spacingY))
        //加载rowitemCounts + 10个item 
        for(let i =0 ; i < this.rowItemCounts + 10 ; ++ i){
            //数据已经加载完毕了 说明需要加载的数据量很小
            if(typeof data[i] == 'undefined')
                break 
            //data[i]为了测试方便实际上只是一个1 2 3这样的数字 具体data和updateItem方法的实现
            //你需要根据你自己的情况来实现 
            item.getComponent('ItemNode').updateItem(data[i])
            //记录一下itemid
            item.__itemID = i 
            //保存item到数组
            this.items.push(item)
            //加入item节点到scrollview的list里面
            this.list.addChild(item)
            //设置x坐标
            item.x = 20
            //设置y坐标 (根据自己设置的不同的锚点这些东西来调整能跑就完事了)
            item.y = - (height / 2 +  i * (height + this.spacingY ))
            //继续创建
            if(i < this.rowItemCounts + 9){
                item = cc.instantiate(this.itemPrefab)
            }
            
        }
        //设置list的高度 不设置无法滑动
        this.list.height = 20 + (data.length) * height + (data.length) * this.spacingY
        //计算顶部最大Y
        this.topMax = (5 * height + 4 * this.spacingY)
        //计算底部最小Y
        this.bottomMax = -(this.view.height + this.topMax)
        //保存list的当前Y坐标
        this.lastListY = this.list.y
    },
    update(){
        //判断是否往下滑动
        let isDown = this.list.y > this.lastListY
        //当前的item数量
        let countOfItems = this.items.length
        //预显示数据的总数量
        let dataLen = this.data.length
        //遍历所有item节点
        for (let i in this.items){
            let item = this.items[i]
            //item坐标转换到对应view节点的坐标 y坐标需要减去一半item的高度...具体看你item的锚点设置
            let itemPos = this.list.convertToWorldSpaceAR(item.position)
            itemPos.y -= this.view.height / 2
            itemPos = this.view.convertToNodeSpaceAR(itemPos)
            //如果是往下滑动
            if(isDown){
                //判断当前item的坐标是否大于顶部最大Y
                if(itemPos.y > this.topMax){
                    //计算新的itmeid 
                    //比如一共13个item item的索引就是0-12 那么第0个item超过y坐标之后 就需要显示第13个item
                    //那么就是将当前id + 当前item的数量即可
                    let newId = item.__itemID + countOfItems 
                    //如果item已经显示完毕了就不需要刷新了
                    if(newId >= dataLen) return 
                    //保存itemid
                    item.__itemID = newId
                    //计算item的新的Y坐标 也就是当前y减去所有item加起来的高度
                    item.y = item.y - countOfItems * this.itemHeight - (countOfItems ) * this.spacingY
                    //刷新item内容 
                    item.getComponent('ItemNode').updateItem(this.data[item.__itemID])
                }
                //如果是往上滑动
            }else { 
                //如果超过底部最小Y 和上面的一样处理一下就完事了
                if(itemPos.y < this.bottomMax){
                    let newId = item.__itemID - countOfItems
                    if (newId < 0) return
                    item.__itemID = newId
                    item.y = item.y + countOfItems * this.itemHeight + (countOfItems) * this.spacingY
                    item.getComponent('ItemNode').updateItem(this.data[item.__itemID])
                }
            }
        }
        //存储下当前listnode的Y坐标 
        this.lastListY = this.list.y
    }
});

加载代码如下

let data = []

for (let i =0;i<10000;++i){


data.push(i)

}

this.scroll.getComponent(‘ScrollView’).init(data)

传递进入10000条数据 实际上也就加载可视区域的数量+10而已 动态不停的刷新就完事了…刷新频率可以降低点 这样消耗会更低.这个破问题经常会面试被问到哎…动手解决一番麻麻再也不担心我被面试官虐了。