美文网首页
Cocos Creator ScrollView 长列表优化

Cocos Creator ScrollView 长列表优化

作者: 宠包狂魔馒头酱 | 来源:发表于2020-12-02 10:22 被阅读0次

    一. 加载一个超长的列表会导致的问题

    1.当场景载入时如果列表很长,那么需要一次性加载所有的条目到列表上,生成条目势必会消耗很长的时间。
    2.当列表上的条目都载入后,因为列表上的条目很多,势必会造成Draw call的上升,严重的话导致滑动时会有卡顿感.


    问题描述.png

    二. 解决方案描述,只适用已知子条目高宽及个数的情况

    大致意思就是只产生用户屏幕可见区域内的条目,当用户滑动列表时动态生成或从缓存池中获取需要的节点


    解决方案.png

    三.代码部分,这里以简单的单行单个条目的列表来叙述,复杂的例如网格形式的布局也可以用相似的方式实现

    条目数据结构:

    // 列表滚动条目列表
    export default class ListScrollItem {
    
        // y轴值
        public y: number = 0
    
        // x轴值
        public x: number = 0
    
        // 是否初始化条目
        public initedNode: boolean = false
    
        // 条目数据
        public data: any = null
    
        // 条目生成后的节点引用
        public node: cc.Node = null
    
        private init (x: number, y: number, data: any) {
            this.x = x
            this.y = y
            this.data = data
        }
    }
    
    1. 设置列表高度在已知条目高度和条目个数的情况下,先生成列表的数据结构,同时算出整个List的高度,然后把高度赋值给ScrollView的Content节点
    @property(cc.ScrollView) 
    scrollView: cc.ScrollView = null
    // 列表条目预制体
    @property(cc.Prefab)
    itemPrefab: cc.Prefab = null
    @property(number)
    listCount: number = 100
    // 列表条目数据结构
    private scrollItems: ListScrollItem[] = []
    
    start () {
      let itemTemplateNode = cc.instantiate(this.itemPrefab)
      let itemHeight = itemTemplateNode.height
      for (var index = 0; index < this.listCount; index ++) {
            let scrollItem = new ListScrollItem()
            // 这里以条目锚点从左上开始计算,cocos默认从中心
            scrollItem.init(0, index * itemHeight, index)
            this.scrollItems.push(scrollItem)
      }
      this.scrollView.content.height = itemHeight * listCount
      // 到此,我们生成了一个有很长高度的ScrollView及列表的数据结构,
      // 接下来就动态的加载子节点,即只生成屏幕可见区域内的条目
    }
    
    
    1. 动态加载屏幕可见区域内的条目
    start () {
      // 注册滚动监听函数
      this.scrollView.node.on("scrolling", this._onScrolling, this)
    }
    
    // scrollView滚动回调
    _onScrolling () {
      // 可视区域y轴最小值
      let visibleAreaMinY = this.scrollView.getScrollOffset().y
      // 可视区域y轴最大值
      let visibleAreaMaxY = visibleAreaMinY + this.scrollView.node.height
      // 循环遍历列表数据结构,检查节点条目的y轴是否处于可见区域内
      this.scrollItems.forEach(scrollItem => {
         let scrollItemInVisibleArea = scrollItem.y >= visibleAreaMinY && scrollItem.y <= visibleAreaMaxY
        if (scrollItemInVisibleArea) {
          // 节点在屏幕区域内
          if (!scrollItem.initedNode) { // 条目未创建,则创建条目
            let itemNode = cc.instantiate(this.itemPrefab)
            scrollItem.node = itemNode
            scrollItem.initedNode = true // 创建标示置为已创建
            // 添加到ScrollView
            this.scrollView.content.addChild(node)
          }
          scrollItem.node.opacity = 255 // 让条目可见
        } else {
          // 节点不在可见区域内
          if (!scrollItem.initedNode) { // 条目未创建,不做操作
            return
          }
          // 条目已创建,隐藏条目,减少Draw call
          scrollItem.node.opacity = 0
        }
      })
    }
    

    完成到这一步后基本已经实现了列表的优化,有效的解决了标题提到的两个问题,但还有一些地方可以优化,

    三. 再优化

    1. 创建条目时总是生成了新的条目。
    2. 每次列表滚动后都是循环遍历检查列表所有的数据结构,感觉还是有点浪费,让我们继续优化它。

    先来优化这一项:1. 创建条目时总是生成了新的条目。

    // 条目缓存池,用来缓存已经不可见的条目,当需要创建新条目时先从缓存池中查找,如果有那么就不用创建新的了
    private scrollItemNodePool: cc.NodePool = new cc.NodePool()
    
    // 修改一下_onScrolling方法
    _onScrolling () {
      // 可视区域y轴最小值
      let visibleAreaMinY = this.scrollView.getScrollOffset().y
      // 可视区域y轴最大值
      let visibleAreaMaxY = visibleAreaMinY + this.scrollView.node.height
      // 循环遍历列表数据结构,检查节点条目的y轴是否处于可见区域内
      this.scrollItems.forEach(scrollItem => {
         let scrollItemInVisibleArea = scrollItem.y >= visibleAreaMinY && scrollItem.y <= visibleAreaMaxY
        if (scrollItemInVisibleArea) {
          // 节点在屏幕区域内
          if (!scrollItem.initedNode) { // 条目未创建,则创建条目
            // 先从缓存池中获取条目
            let itemNode = this.scrollItemNodePool.get()
            // 如果缓存池中没有条目,那么在创建
            if (!itemNode) {
              itemNode = cc.instantiate(this.itemPrefab)
            }
            
            scrollItem.node = itemNode
            scrollItem.initedNode = true // 创建标示置为已创建
            // 添加到ScrollView
            this.scrollView.content.addChild(node)
          }
          scrollItem.node.opacity = 255 // 让条目可见
        } else {
          // 节点不在可见区域内
          if (!scrollItem.initedNode) { // 条目未创建,不做操作
            return
          }
          
          // 条目已创建,隐藏条目,减少Draw call
          scrollItem.node.opacity = 0
          
          // 添加到缓存池中
          this.scrollItemNodePool.put(scrollItem.node)
          scrollItem.initedNode = false // 创建标示置为未创建
        }
      })
    }
    

    修改后的_onScrolling方法有效的重用了条目,不用每次都创建新的.

    1. 每次列表滚动后都是循环遍历检查列表所有的数据结构,感觉还是有点浪费,让我们继续优化它。 针对这个问题的优化我的解决办法有点复杂,暂时不贴出来了,有想法的朋友可以出出意见。

    未完待续

    相关文章

      网友评论

          本文标题:Cocos Creator ScrollView 长列表优化

          本文链接:https://www.haomeiwen.com/subject/nbbswktx.html