COCOS CREATOR的scroll view性能优化记录

本文介绍了一种优化Scrollview性能的方法,通过仅加载可视区域内的item并预留缓冲区,有效减少内存消耗和提高加载速度,避免大量数据加载时的卡顿现象。

以前的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的时候并没有考虑多行多列的情况,仅仅是考虑了列表模式,用来做一些排行榜,任务列表是足够使用的。下面上代码,额代码写的不好,如有bug请大佬指教:

scrollview.js

cc.Class({
    extends: cc.Component,

    properties: {
        // foo: {
        //     // ATTRIBUTES:
        //     default: null,        // The default value will be used only when the component attaching
        //                           // to a node for the first time
        //     type: cc.SpriteFrame, // optional, default is typeof default
        //     serializable: true,   // optional, default is true
        // },
        // bar: {
        //     get () {
        //         return this._bar;
        //     },
        //     set (value) {
        //         this._bar = value;
        //     }
        // },
        //scrollview的view剪裁节点
        view:cc.Node,
        //scrollview的具体装载item的node
        list:cc.Node,
        //item的prefab
        itemPrefab:cc.Prefab,
        //item的间隔
        spacingY:0,
    },

    // LIFE-CYCLE CALLBACKS:

    // onLoad () {},

    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
    },
    //我测试时使用的data array 只是一个int数组 [0,1,2,3,4]这样
    //itemprefab的updateitem方法如下
    // updateItem(data) {
    //     this.itemIndex.string = data //刷新标签
    // }
    //使用需调用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
    }
    // update (dt) {},
});

加载代码如下

let data = []
        for (let i =0;i<10000;++i){
            data.push(i)
        }
        this.scroll.getComponent('ScrollView').init(data)

具体测试效果:

 

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

<think>好的,我现在需要帮助用户查找在Cocos Creator中实现ScrollView功能的示例教程。根据用户提供的引用内容,我需要整合这些信息,给出结构清晰的步骤和示例代码。首先,我应该回顾用户提供的引用内容,找出与ScrollView实现相关的部分。 引用[1]提到了两种添加事件的方法,其中方法二使用scrollview.node.on来绑定事件,如'scroll-to-top',并在回调函数中处理事件。这里还给出了一个代码示例,展示了如何在组件的onLoad方法里注册事件,并提到customEventData无法通过这种方式传递。 引用[2]简要说明了ScrollView和PageViewCocos Creator中的滚动容器组件,属于ViewGroup和Component的一部分,这可能帮助用户理解组件的基本结构。 引用[3]展示了另一个回调函数的例子,其中参数包括scrollview组件实例、事件类型和自定义数据customEventData。这里的事件处理方式与方法二不同,可能涉及不同的事件绑定方法。 引用[4]和[5]讨论了ScrollView性能优化,指出在处理大量条目时的性能问题,并提到需要根据项目情况进行优化,如MyScrollView的实现,动态调整节点数量,减少渲染开销等。这可能对用户后续优化有帮助,但当前用户主要需要实现方法,这部分可以作为附加内容提及。 接下来,我需要整理实现ScrollView的基本步骤。通常,在Cocos Creator中创建ScrollView需要以下步骤: 1. 在场景中添加ScrollView组件,设置其内容节点(如content)和滚动方向。 2. 配置滚动条(ScrollBar)和视图区域(View)。 3. 动态生成或添加子项到content节点下。 4. 处理滚动事件,如滚动到顶部、底部或其他位置时的回调。 根据引用中的代码示例,可以给出两种事件绑定方式:一种是通过节点事件(如引用[1]),另一种是通过ScrollView组件的事件(如引用[3])。需要区分这两种方法的不同之处,并说明参数传递的差异。 此外,动态生成内容可能需要使用预制体(Prefab),在运行时实例化并添加到content节点中,同时调整content的高度或宽度以容纳所有子项。性能优化方面,可以引用[4]和[5]提到的内容,比如复用节点、减少同时存在的节点数量、节流滚动事件等,但用户当前可能需要先实现基础功能,优化部分可以作为附加建议。 在代码示例部分,需要确保语法正确,比如正确使用cc.Class定义组件,设置properties,并在onLoad中绑定事件。同时,注意事件类型的不同,如'scroll-to-top'和通过枚举类型cc.ScrollView.EventType.SCROLLING的事件处理。 可能还需要解释ScrollView的相关属性,比如content、horizontal和vertical滚动方向的启用,以及滚动条的具体设置步骤。 最后,相关问题部分需要根据引用的内容生成,例如事件绑定方法、性能优化、动态加载数据等,帮助用户进一步了解ScrollView的其他方面。</think>在Cocos Creator中实现$ScrollView$功能可分为以下步骤,包含基础实现和事件交互: --- ### 一、基础实现步骤 1. **添加$ScrollView$组件** - 在场景中创建节点,添加$cc.ScrollView$组件 - 设置$content$属性指向内容容器节点(用于放置滚动内容) - 启用横向/纵向滚动:勾选$Horizontal$或$Vertical$ 2. **配置滚动条** - 创建$ScrollBar$节点并绑定到$ScrollView$的$ScrollBar$属性 - 设置滚动条方向与$ScrollView$滚动方向一致 3. **动态生成内容示例** ```javascript cc.Class({ extends: cc.Component, properties: { scrollview: cc.ScrollView, itemPrefab: cc.Prefab //预制体 }, onLoad() { const content = this.scrollview.content; //生成10个子项 for (let i = 0; i < 10; i++) { let item = cc.instantiate(this.itemPrefab); content.addChild(item); } //调整内容容器高度 content.height = 10 * item.height; } }); ``` --- ### 二、事件交互实现 #### 方法一:通过组件直接绑定 ```javascript this.scrollview.node.on('scroll-to-top', (event) => { const scrollviewComp = event.detail; //获取组件实例[^1] console.log("滚动到顶部"); }, this); ``` #### 方法二:使用$ScrollView$事件枚举 ```javascript this.scrollview.node.on(cc.ScrollView.EventType.SCROLLING, (scrollview, eventType) => { if (eventType === cc.ScrollView.EventType.SCROLL_ENDED) { console.log("滚动结束"); } }, this, "customData"); //可传递自定义参数[^3] ``` --- ### 三、性能优化建议 1. **复用节点**:使用对象池管理预制体,避免频繁创建/销毁节点[^4] 2. **动态加载**:根据滚动位置计算可视区域,仅渲染可见项(参考$MyScrollView$优化方案[^5]) 3. **减少嵌套**:简化$content$子节点的层级结构以降低渲染开销 4. **节流处理**:对滚动事件添加节流限制,避免高频触发逻辑 --- ### 相关问题
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值