讲讲海量数据列表如何优化。

列表可能是游戏里经常用到的控件之一了。制作列表时有个很大的误区就是一条数据对应一个显示对象,海量数据时会导致场景中节点太多,不仅浪费内存,还会导致帧率急剧下降。
所以,可以参考循环列表的做法,只初始化一屏显示对象,轮流用以展示数据。
为此,我写了两个组件,代码开源在 cococ_creator_proj_base

具体做法参见循环列表

使用说明如下

1.ListView, 循环滚动列表,固定尺寸item, 屏幕可见范围外item会回收等待下次复用。支持横向,竖向,多行多列。此组件适合用来做界面内各种列表。

  • 初始化,传入item模板节点(cc.Node),设置各种回调函数
   @property(cc.ScrollView)
    scrollview: cc.ScrollView = null;

    @property(cc.Node)
    mask: cc.Node = null;

    @property(cc.Node)
    content: cc.Node = null;

    @property(cc.Node)
    item_tpl:cc.Node = null;

    private list:ListView.ListView;

    on_show(...params)
    {
        this.list = new ListView.ListView({
            scrollview:this.scrollview,
            mask:this.mask,
            content:this.content,
            item_tpl:this.item_tpl,
            cb_host:this,
            item_setter:this.list_item_setter,
            select_cb:this.list_item_onselect,
            recycle_cb:this.list_item_onrecycle,
            column:1,
            gap_y:10,
            direction:ListView.ListViewDir.Vertical,
        });
        this.list.set_data(Consts.AllStages);
    }
  • 设置item回调函数
 list_item_setter(item:cc.Node, desc:Consts.StageDesc, index:number):void
     {
        const isOpen = appdata.getStageOpenState(desc.stage, desc.unlockcond, desc.total);
        const isPassed = appdata.isStagePassed(desc.stage, desc.total);
        const cond = item.getChildByName("cond");
        const txt_cond = cond.getChildByName("txt_cond");
        const txt_progress = item.getChildByName("txt_progress");
        const btn_share = item.getChildByName("btn_share");
        const img_star = item.getChildByName("img_star");
        const gold_star = img_star.getChildByName("gold_star");
        //省略
    }

如果要做item尺寸不一的滚动列表,则可参见循环滚动列表

使用方法如下:
2.ScrollView, 循环滚动列表,支持不定尺寸的item, 屏幕可见范围外item会回收等待下次复用。支持横向,竖向, 但不支持多行多列。特别适合用来做聊天模块。

  • 初始化,传入item模板节点(cc.Node)列表,设置各种回调函数
   const templates:ScrollItemTemplate[] = [
      {key:MsgType.ROUND_START.toString(), node:this.item_roundstart},
      {key:MsgType.LEFT_REDPACK.toString(), node:this.item_leftpack},
      {key:MsgType.RIGHT_REDPACK.toString(), node:this.item_rightpack},
      {key:MsgType.GRAB_NOTIFY.toString(), node:this.item_grab},
      {key:MsgType.ROUND_END.toString(), node:this.item_common},
      {key:MsgType.ROUND_RESULT.toString(), node:this.item_roundresult},
      {key:MsgType.LEFT_CHAT.toString(), node:this.item_leftchat},
      {key:MsgType.RIGHT_CHAT.toString(), node:this.item_rightchat},
      {key:MsgType.JOIN_ROOM_NOTIFY.toString(), node:this.item_common},
      {key:MsgType.QUIT_ROOM_NOTIFY.toString(), node:this.item_common},
      {key:MsgType.DISMISS_ROOM_NOTIFY.toString(), node:this.item_common},
      {key:MsgType.ROUND_TIMEOUT_NOTICE.toString(), node:this.item_common},
   ];
   this.scview = new ScrollView({
       scrollview:this.scrollview,
       mask:this.mask,
       content:this.content,
       item_templates:templates,
       cb_host:this,
       item_setter:this.item_setter,
       recycle_cb:this.list_item_recyle,
       gap_y:10,
       auto_scrolling:true,
       direction:ScrollDirection.Vertical,
   });
  • 设置item回调内部根据传入的key及data为对应item节点设置数据
   item_setter(item:cc.Node, key:string, data:any, index:number):[number, number]
   {
     const enum_key:number = parseInt(key);
     switch(enum_key)
     {
         case MsgType.ROUND_START:
             item.getComponent(cc.Label).string = format.sprintf("文本%d", data);
             return [item.width, item.height];

         case MsgType.LEFT_REDPACK:
         case MsgType.RIGHT_REDPACK:
             return this.set_pack_item(item, data);

         case MsgType.ROUND_RESULT:
             let node_names:cc.Node = item.getChildByName("names");
             node_names.getComponent(cc.Label).string = data;
             item.height = node_names.height - node_names.y;
             return [item.width, item.height];

         case MsgType.LEFT_CHAT:
         case MsgType.RIGHT_CHAT:
             return this.set_chat_item(item, data);

         case MsgType.GRAB_NOTIFY:
             return this.set_grab_item(item, data);

         case MsgType.DISMISS_ROOM_NOTIFY:
         case MsgType.ROUND_TIMEOUT_NOTICE:
         case MsgType.JOIN_ROOM_NOTIFY:
         case MsgType.QUIT_ROOM_NOTIFY:
         case MsgType.ROUND_END:
             item.getComponent(cc.Label).string = data as string;
             return [item.width, item.height];

         default:
             return [0, 0];
     }
   }
  • 追加数据, 传入key及item数据
const notify:pb.IRoomChatNotify = resp.roomChatNotify;
const key:number = notify.sender.acc == appdata.user.acc ? MsgType.RIGHT_CHAT : MsgType.LEFT_CHAT;
let data:ScrollItemData = {key:key.toString(), data:notify};
this.scview.append_data(data);
8赞

欢迎使用,及提出优化意见

有没有js版的. 看不懂这些:sweat_smile::sweat_smile:

我想要一个结合nodepool和layout的版本,layout自动排版不用关心大小和位置,用zIndex决定排序。我卡在重复利用的算法上了,希望楼主能指点一下

看得懂js,看不懂ts?

就是想重复利用视界外的item? 那么你应该可以判断item已经不在视界内了吧

是的,听起来挺简单。但是我却写出各种死循环,导致网页各种卡,我都怀疑我的猿生了。
layout移除item后,后面的item会自动补位到前面,估计这个方案还有待各位大神的研究。
明明就有一个非常好用的自动排版缺不能用的尴尬,估计我最近郁闷了

你是要做什么功能,需要用到自动补位的功能

游戏里的评论版啊

this.mask传什么呢 大佬?

先占楼一下看看

mark一下 说不定以后会用到

这种不定高的列表并不是最好的实现
最好的实现是prefab的高度是不可知的,随时都可变的
我们自己写了个,目前好多BUG~~~:3:

评论板可以用这个做啊。用scrollview,我的聊天就是用这个做的。可以支持多个item模板

我这里的列表项就是不定高的啊,给列表项设置数据的时候你要计算出它的高度,才能正确给你排版。
你说的prefab随时可以变,有这种需求?

用了 下 感觉滑动 贼卡

1赞

大哥,你确定用了吗,我这组件主要是优化item数量过多的问题,底层滑动还是scrollview,不知道你怎么使用导致滑动贼卡?

你要是觉得有问题可以先看代码呀,代码经得起体验

老哥 我用了 把你的翻译成 js了 虽然是只渲染一屏 但是滑动的时候确实卡 在web上跑的挺流畅的 放到微信小游戏上就不行了

挺好的. 去年用 cc写了 不定高的UIListView. cell 高度可跟随内容变化(使用Layout组件) ,.

使用只需要传入复用cell 模板. 以及数据源,支持设置header,footer,topMargin,bottomMargin,spaceing,下拉刷新,上拉加载更多.

开发类似微博Feed 列表. iPhone6sp 基本在 58~60fps之间.

demo.zip (2.6 MB)

2赞