Cocos Creator ScrollView 长列表优化

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

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 长列表优化)