实现scrollView里单元格的复用附上详细教程和demo

在开发游戏的过程中,不论什么时候都会有类似于排行榜这样子的界面。如果一个排行榜只有10个人。那么我们一个for循环就可以搞定,如果有100个人,或者1000个人。那么直接简单粗暴的创建100个,1000个节点上去,不但会导致你的手机游戏fps直线下降。而且,在创建的那一瞬间,你的手机会卡顿个四五秒。所以只创建少量的单元节点用于显示包含了几百上千的数据是一个很好的办法。有过cocos2dx C++语言的开发者,应该知道复用单元格有一个现成的解决方案分别涉及到TableView,TableViewCell和 TableViewDelegate这三个类的使用。而在Creator里却无法使用这三个东西,所以我借鉴了这个思想,自己写了一个简单的单元格复用解决方案。来分享给大家。下边上图。


如上图,这是一个简单的排行榜,这里边存放了1000条数据。但是可以看到,我们的drawCell和 fps还是非常给力的。因为在滑动过程中,超出了视野之外的节点会被从ScrollView上移除加入到复用池子。然后新的单元格即将出现的时候会从复用池找到一个空闲的节点,拿来直接用。所以整个场景上最多只创建了七八个节点而已。

接下来是我们的详细教程。我们首先创建一个prefab。表示每一个单元格。


我们在单元格的prefab上挂了一个组件 rankItemCtrl 来控制这个节点。下边是rankItemCtrl的详细代码。

cc.Class({
    extends: cc.Component,
    properties: {
        label:cc.Label,
    },
    start () {

    },

    //当节点被再利用的时候。这个函数会被触发
   reuse:function (i) {
    this.label.string = "我是排行第" + i;
   },

    //当节点被回收的时候,这个函数会被触发
    unuse:function () {
    } ,
});

注意,我们控制单元格的脚本里必须有 reuse 函数和 unuse 函数。

接下来,我们在我们的场景里创建一个节点 rankLayer。在这个节点上放置一个普通的UI节点-ScrollView节点。 然后在 rankLayer 挂上 rankLayerCtrl这个脚本。

rankLayerCtrl脚本的代码如下

cc.Class({
extends: require("TableViewCtrlV"),

properties: {

},

// LIFE-CYCLE CALLBACKS:

// onLoad () {},

start () {
    //模拟数据回来需要2秒钟
    setTimeout(function () {
        this.scrollView.node.active = false;
        for(var i=0; i< 1000; i++){
            this._sourceData.push(i);
        }
        this.scrollView.node.active = true;
        this.reload();
    }.bind(this), 2000);
},

onDestroy:function () {
    //不要忘记调用这句话,来清除复用池子里的节点
    this.clearReuserArray();
},

//请重写这个函数
getItem:function (i) {
    var item = this._reuseArray.pop();
    if(item == null){
        item = cc.instantiate(this.itemPrefab);
    }

    item.getComponent(this.itemCtrlName).reuse(this._sourceData[i]);
    return item;
}
});

我们在rankLayerCtrl的onLoad里做了一个延迟2秒的初始化,模拟获取数据需要的时间。然后将获取的数据放置到 this._sourceData数组里。

在数据获取之前,我们现将scrollView这个节点隐藏,在数据回来填充了this._sourceData之后,再将ScrollView节点给开启。注意一定要这样子做。最后我们调用了this.reload()函数,刷新单元格。

接着我们看 getItem函数。当我们滑动排行榜的时候,会不断的触发getItem函数。这里,我们需要根据传入的下标i。来返回合适内容的单元格。
var item = this._reuseArray.pop();
if(item == null){
item = cc.instantiate(this.itemPrefab);
}
这段代码的作用是现在复用池子里选取一个空闲的单元格,如果没有就实例化一个新的单元格。

接着我们注意到了我们的rankLayerItem继承自 TableViewV.js这个脚本。没错,单元格复用的计算都是放在这个脚本的 update 函数里的。所以我们继承的脚本千万不能在使用 update 这个函数了。同时在这个脚本里使用了underscore.js这个脚本。我们需要将underscore.js设置为插件才行。
最后我们使用到的脚本其实只有三个
TableViewH.js。横向的复用单元格
TableViewV.js。 竖向的复用单元格
underscore.js 设置为插件。
在TableViewV.js的文件里写有如下注释
/*

  • 1.将这个组件挂在scrollView上来实现单元格的复用(类似于TableView),这是竖向滑动的单元格,从上到下
  • 2.在使用前将scrollView/view的Anchor设置为(0.5,0)
  • 3.在使用前将scrollView/view/content的Anchor设置为(0.5,0)
  • 4.使用者只需要关注getItem这个函数即可
  • 5.不要在 scrollView/view/content 上挂载widget组件
  • */
    请仔细阅读并设置好哦。目前只支持从上到下,或者从左到右的滑动。而且要求每个格子的高度是一致的。

完成的demo也一起上传了。大家可以下载一下,配合着这篇教程使用。

recicleDemo.zip (223.6 KB)

如果在外边没有时间下载这个demo的同学也可以体验一下下边这个微信小游戏,里边的世界排行榜和商店就是用这个写出来的。打开之后各种滑动也是很流畅的哦。

8赞

干货满满,感谢分享。

能不能顺便支持一下小游戏哈:2:

underscore是做什么用呀?

真是不错哈,给个赞

是一个第三方的js库。你导入到你的项目里,设置为 插件

好用,顶一下

mark!

微信小游戏吗??这个可以直接拿来用啊?子域的排行榜也可以直接拿来用啊

在子域里使用了这个,还是会感觉到卡顿,但是玩了会你的小游戏感觉排行榜还是挺流畅的,想请问一下你是有对子域做其他的优化吗?

没有啊,我的子域就是用的这个单元格复用做的说。

你的世界排行榜是做在子域的吗?

好友是子域,世界是主域的

underscore 求问 这个库里面的都是计算什么的呀,代码里使用的哪些都是计算什么的

这个不错,找了论坛许多写的tableView的大部分删除和添加数据都有问题,这个可以。