滚动列表,这种东西在游戏中很常见.而cocos creator 中的ScrollView + Layout 只有你想不到,没有它满足不了,   各种分骚布局. 都能实现.  但是, 但是, 它还有一些场景不是很适合.例如 :

  • 千百人的排行榜(虽然,大家只会记得第一名,谁还在乎第二名,除非第二名是自己)
  • 密密麻麻的好友列表(当然有些人是没有几个朋友的,就像我,这点就不适用了)
  • 上百条的充值记录(当然,只有我这种土豪,才会充这么多给自己开发的游戏)

这些数据,都有一个特点,那就是,数据量大,结构类似 对于这种数据,我们可以使用scrollView 进行显示,其实压力也不是很大,但是,如果用 循环列表 的话,会更好. 那么,  你用还是不用呢?

进入正题.

2019.10.23 翻了以前的测试项目找到了个demo附加在这,

链接: https://pan.baidu.com/s/1ukiVbbU3SyoajHAU24Z9qg 提取码: yup6

js版本是这篇博客用到的,后面的ts版本是项目用到的稍微改了一下,做了个小demo也都传上来(creator版本 2.2.0)


ScrollView的结构:

之前无论是 2dx 还是 2d-JS 都用过ScrollView,而且用的也非常多,但是呢,今天我想用creator 来说说scrollView的那些事:

scollView的原理就是用content做子元素的容器,用view做一个切割,显示View元素在屏幕占据的矩形面积,其子元素,只能显示在父元素范围内的信息.view 和content 其实是实现scrollView的关键

其实scrollView 组件本身起到的作用,  就是主要是 统筹的作用 ,将数据逻辑,和页面截取分开 

  • content本身不做任何处理,由scrollView 组件控制,
  • scrollview 为滑动提供动画,控制范围.联动滑动条,提供事件监听
  • content可以添加Layout组件,布局元素的内容

滚动列表 ListView的实现

以前的公司,为了实现ListView 在将content向上,或者向下滚动的时候,当某个元素完全滚出view显示的范围一定距离后,会将content回滚,然后将之前显示的content中的其他item设置到,回滚之前,相对于屏幕的位置.将超出范围的那个元素放置到队列最后,这样操作在一帧内完成,玩家不会意识到页面回滚了.屏幕可以接着滚动.这样会使content保存在一个固定的大小.在需要显示滚动条的时候就比较麻烦.

这算是一种实现方法.但是呢,还不是最好的解决办法.

而我今天要说的就是第二种方式:

这种方式,在创建多个类表项的时候,只需要创建超过屏幕范围数量的Item,然后,只需要增加content的长度,到所有item的长度之后,就可以了,在滑动的时候,按照上面的逻辑处理,就可以实现循环滚动.


代码实现ListView:

属性配置:

 properties: {itemTemplate: { // Item的模板default: null,type: cc.Node},scrollView: {   //需要的ScrollViewdefault: null,type: cc.ScrollView},spawnCount: 0, //循环的Item 的个数totalCount: 0, //总共需要创建多少个Itemspacing: 0,    //item 间隙bufferZone: 0, //出了scrollView多远,开启位置调整},

初始化:

 onLoad: function () {this.content = this.scrollView.content;     //ScrollVeiw的content节点this.items = [];                            //存放所有的创建的Item 其实只有循环的几个                //this.lastContentPosY = 0;                   //用来保存content在Y坐标的值//根据循环需要的数量调整content的高度this.content.height = this.totalCount * (this.itemTemplate.height + this.spacing) + this.spacing; //创建必要的Itemfor (let i = 0; i < this.spawnCount; ++i) { let item = cc.instantiate(this.itemTemplate);this.content.addChild(item);//第一个item的位置是     - (间隔高度 + 一半高度的item);  后面以此减去间隔,和一item的高度item.setPosition(0, -item.height * (0.5 + i) - this.spacing * (i + 1));item.getComponent('Item').updateItem(i, i);this.items.push(item);}},

循环判断,更新数据:

    getPositionInView: function (item) { //位置转换到ScrollVeiw的坐标系,判断是否出了可视范围let worldPos = item.parent.convertToWorldSpaceAR(item.position);let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);return viewPos;},update: function(dt) {let items = this.items;let buffer = this.bufferZone;let isDown = this.scrollView.content.y < this.lastContentPosY; let offset = (this.itemTemplate.height + this.spacing) * items.length;for (let i = 0; i < items.length; ++i) {let viewPos = this.getPositionInView(items[i]);if (isDown) {// if away from buffer zone and not reaching top of contentif (viewPos.y < -buffer && items[i].y + offset < 0) {items[i].y = items[i].y + offset;let item = items[i].getComponent('Item');let itemId = item.itemID - items.length; // update item iditem.updateItem(i, itemId);}} else {if (viewPos.y > buffer && items[i].y - offset > -this.content.height) {items[i].y = items[i].y - offset;let item = items[i].getComponent('Item');let itemId = item.itemID + items.length;item.updateItem(i, itemId);}}}this.lastContentPosY = this.scrollView.content.y;},

以上,便是一个垂直滚动的ListView的整个逻辑了,这里,我没有直接创建一个ListVeiw 继承ScrollView原因是,降低了耦合性,这种组合模式拆卸也方便,可以更具需要再特化这个组件. 第二,创建继承后,你还是要手动搭建一个ScrollIVew,太麻烦(主要是,我不会创建那种自定义组件,从工具页面拖出来就配置好子节点的那种.)

最后,福利环节:

自己写的一个简单的ListVIew,欢迎指正:

想把资源包导出来上传,好像不行,就把这个组件贴在这了

代码进过cocos creator 2.07测试

// Learn cc.Class:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/class.html
//  - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/class.html
// Learn Attribute:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
//  - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
//  - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/life-cycle-callbacks.html//测试用,可以删除
var ItemData = cc.Class({name:"ItemData",properties: {id:cc.Integer,name:cc.String,grade:cc.Integer},});
const EventType=cc.ScrollView.EventType;/***  ListView 方向* @enum ListView.Direction*/
const Direction = cc.Enum({HORIZONTAL: 0,VERTICAL:   1
});/***  循环滚动组件* @class ListView* @extends Component*/
let ListView =cc.Class({extends: cc.Component,properties: {template: {default: null,type: cc.Node,tooltip:"跟新数据模版,可以是node节点,也可以是 prefab(手动赋值)"},updateComp: {default:"",tooltip:"跟新数据组件名"},datas:{default: [],type:ItemData,serializable:true},scrollView: {default: null,type: cc.ScrollView},direction:{default: Direction.VERTICAL,type: Direction,},lblPostion:{default: null,type: cc.Label},spawnCount: 0,              //用来循环的item的数量spacing: 0,                 //每个Item的之间的间隙bufferZone: 0,              //离StrollView中心多远之后开启循环_items:[],                  //缓存包装后的Item_updateTimer:0,             //记录上次更新的时间_updateInterval:0.2,        //记录跟新的间隙_content:null,              //ScrollView的容器_deadZone:2,                //死区,滑动在这个范围内,不更新_lastContentPos:cc.v2(0,0), //和死区配合使用_offset:0,                  //_onFastRefresh:false,       //在设置固定位置时,要快速的滚动到相应的位置},statics: {Direction:Direction,},onLoad: function () {this._content = this.scrollView.content;this._lastContentPos = this._content.position;//测试获取数据cc.loader.loadRes("data",function(err,data){this.bindData(data.json);}.bind(this))},//用于手动初始化数据initialize: function (scrollView,direction,template,updateComp,datas,spawnCount,bufferZone) {this.scrollView = scrollView;this.direction  = direction;this.template   = template;this.updateComp = updateComp;this._content   = scrollView.content;if(this.direction == Direction.VERTICAL ){this._content.height = this.datas.length * (this.template.height + this.spacing) + this.spacing;this.spawnCount = spawnCount? spawnCount  : Math.ceil(this.scrollView.height*2/ this.template.height);this.bufferZone = bufferZone ? bufferZone : this.scrollView.height/2+this.template.height;}else {this._content.width = this.datas.length * (this.template.width + this.spacing) + this.spacing;this.spawnCount = spawnCount? spawnCount: Math.ceil(this.scrollView.width*2/ this.template.width);this.bufferZone = bufferZone ? bufferZone : this.scrollView.width/2+this.template.width;}//可以重复使用this._items = [];this.datas = [];this._content.removeAllChildren(true);this.bindData(datas);},bindData:function(datas){cc.assert(datas != null,"数据类型错误");for (let item in datas){this.addItem(datas[item])}},addItem:function(data){//如果数量没有达到循环需要的数量,就要添加新的itemif(this._items.length < this.spawnCount){let item = cc.instantiate(this.template);this._content .addChild(item);let index = this._items.length;if(this.direction == Direction.VERTICAL ){item.setPosition(0, -item.height * (0.5 + index) - this.spacing * (index + 1));}else {item.setPosition(item.width * (0.5 + index) + this.spacing * (index + 1),0);}item.getComponent(this.updateComp).updateItem(data);//包装一下保存indexthis._items.push({_item:item,_index:index});}this.datas.push(data);//添加元素后,重新计算一下content的高度if(this.direction == Direction.VERTICAL ){this._content.height = this.datas.length * (this.template.height + this.spacing) + this.spacing;}else {this._content.width = this.datas.length * (this.template.width + this.spacing) + this.spacing;}},getPositionInView: function (item) {let worldPos = item.parent.convertToWorldSpaceAR(item.position);let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);return viewPos;},update: function(dt) {if(this._items.length < this.spawnCount){return;}this.lblPostion.string= this._content.position.toString();//可以启用这里的跟新,也可以是onScroll函数return;this._updateTimer += dt;if(this._updateTimer < this._updateInterval ) return;this._updateListView();this._updateTimer = 0;this._lastContentPos = this._content.position;},_updateListView:function(){// 和 onScroll 函数开启一个就够了let items = this._items;for (let i = 0; i < items.length; ++i) {var item =items[i];let _item   = item._item;let _index  = item._index;let viewPos = this.getPositionInView(_item);let buffer  = this.bufferZone;let offset =0;if( this.direction == Direction.VERTICAL){offset=(this.template.height + this.spacing) * this._items.length;if(this._content.y > this._lastContentPos.y){if (viewPos.y > buffer && _item.y - offset > -this._content.height) {_item.y = _item.y - offset;item._index = _index + this._items.length;let itemComponent = _item.getComponent(this.updateComp);itemComponent.updateItem(this.datas[item._index]);}}else{if (viewPos.y < -buffer && _item.y + offset < 0) {_item.y = _item.y + offset;item._index = _index - this._items.length;let itemComponent = _item.getComponent(this.updateComp);itemComponent.updateItem(this.datas[item._index]);}}}else{offset=(this.template.width + this.spacing) * this._items.length;if(this._content.x > this._lastContentPos.x){if (viewPos.x > buffer && _item.x - offset > 0) {_item.x= _item.x - offset;item._index = _index - this._items.length;let itemComponent = _item.getComponent(this.updateComp);itemComponent.updateItem(this.datas[item._index]);}}else{if (viewPos.x < -buffer && _item.x + offset < this._content.width) {_item.x = _item.x + offset;item._index = _index + this._items.length;let itemComponent = _item.getComponent(this.updateComp);itemComponent.updateItem(this.datas[item._index]);}}}}},/*** @method scrollEvent* @param {cc.ScrollView}* @param {cc.ScrollView.EventType}*/scrollEvent: function(sender, event) {switch(event) {case EventType.SCROLLING:this.onScroll();break;case EventType.SCROLL_ENDED:cc.log( "Auto scroll ended");break;}},onScroll:function(direction){//和  _updateListView  选择开启一个就够了    //本来是为了简便,不想做太多的判断,反倒是写的更加麻烦了 var diff = this._lastContentPos.sub(this._content.position).mag();if(diff<this._deadZone){return ;}var direction =cc.v2();if(this.direction == Direction.VERTICAL ){direction.y= this._lastContentPos.y> this._content.position.y ? -1 : 1;}else {direction.x= this._lastContentPos.x> this._content.position.x ? -1:1;}for (let i = 0; i < this._items.length; ++i) {let  item   = this._items[i];let _item   = item._item;let _index  = item._index;let buffer  = this.bufferZone;let offsetPos = cc.v3((this.template.width  + this.spacing) * this._items.length * -direction.x ,(this.template.height + this.spacing) * this._items.length * -direction.y,0);let targetPos = _item.position.add(offsetPos);let rect = this._content.getBoundingBoxToWorld()let viewPos = this.getPositionInView(_item);let distance = viewPos.mag();//距离在边框之外 并且   转换后的坐标在盒子空间内if(distance > buffer && viewPos.normalize().equals(direction) && rect.contains(this._content.convertToWorldSpaceAR(targetPos)) ){_item.position = targetPos;item._index = _index +  this._items.length * ( direction.x == -1 || direction.y == 1 ? 1 :-1);let itemComponent = _item.getComponent(this.updateComp);itemComponent.updateItem(this.datas[item._index]);}}this._lastContentPos = this._content.position;},scrollToFixedPosition: function (pos) {this.scrollView.scrollToOffset(pos, 2);},scrollTo500: function () {this.scrollToFixedPosition(cc.v2(500, 500));},scrollToBottom: function () {this.scrollToFixedPosition(cc.v2(0, this._content.height));},scrollToTop: function () {this.scrollToFixedPosition(cc.v2(0, 0));},scrollToLeft: function () {this.scrollToFixedPosition(cc.v2(0, 0));},scrollToRight: function () {this.scrollToFixedPosition(cc.v2(this._content.width, 0));}
});

结束了, 各位再会!, 后面,会写一篇ScrollView + Layout布局的,总结.欢迎吐槽.

吐槽是一种年轻的沟通方式!

2021.1.28 优化

优化后的链接

cocos creator 滚动列表 ListView相关推荐

  1. 《Cocos Creator游戏实战》滚动数字

    滚动数字 创建节点 滚动原理 编写脚本 该功能已收录在Many Widgets插件中,使用Cocos Creator 3.x版本的小伙伴可以用该插件快速生成滚动数字. 插件地址:https://sto ...

  2. Cocos Creator学习目录

    目录 安装和启动 文件结构 编辑器基础 基本概念 (场景树 节点 坐标 组件 ) Cocos Creator 脚本简介 Cocos Creator调试 节点 cc.Node 组件开发cc.Compon ...

  3. cocos creator 学习随笔 day03 节点和组件属性

    目录 节点本身属性 控件属性 场景 空节点 3D对象 2D对象 UI组件 光线 特效 摄像机 地形 节点本身属性 第一栏为节点名,可修改,前面得勾,是表示是否使用该节点,去掉代表隐藏该节点,但是该节点 ...

  4. Cocos Creator v1.5发布:物理集成、2D摄像机、TypeScript

    经过1个多月的Beta版发布和社区测试,直至跳票两周之后,我们终于能够发布 Cocos Creator v1.5 正式版了.这个版本又给大家带来了很多实用的新功能. Cocos Creator 作为第 ...

  5. 游戏滚动列表的优化(降低drawcall从154降低到14,图片大小,界面逻辑)

    前因: 领导对于商城按钮点击切换卡顿非常不满,趁这次商城修改,顺便研究一下优化方案. 观察: 打开cocoscreator show FPS按钮,可以看到一些端倪. 第一眼看到drawcall比较高: ...

  6. Cocos Creator |《飞刀大乱斗》开发教程

    本篇文章转载自公众号[一枚小工],作者:一枚小工 本篇文章为大家带来 Cocos Creator 飞刀大乱斗开发系列教程. one 1 主页下方列表选项如何实现 预览效果 一.具体内容 游戏开始后,加 ...

  7. Cocos Creator 2.0.1 正式发布

    2.0.0 是一个经历了大量底层重构的全新版本,部分开发者在升级项目过程中遇到了一些恼人的问题,这些问题我们一一收集并尽全力解决,现在为大家带来 2.0.1 版本.这个版本除了大幅度提升稳定性和项目升 ...

  8. 用Vue来实现音乐播放器(十六):滚动列表的实现

    滚动列表是一个基础组件  他是基于scroll组件实现的 在base文件夹下面创建一个list-view文件夹 里面有list-view.vue组件 <template><!-- 当 ...

  9. Cocos Creator快速开通联网服务教程

    继集成Egret编辑器工作流后,在最新的Cocos Creator v2.0.7 版本中, Creator服务面板也集成了游戏服务器引擎Matchvs的联网服务.现附上开通教程,方便大家更快上手. 1 ...

最新文章

  1. 一个考察函数基础知识的题
  2. C/C++动态二维数组的内存分配和释放
  3. NVIDIA团队:利用神经网络生成极慢视频
  4. windows 关闭端口被占用脚本
  5. 测试晶面间距软件_超逼真动图解析常用15大分析测试仪器,必收藏!SEM, 红外,紫外,核磁,质谱,TEM,ICP等...
  6. 简短的python金融数据分析师_让一位数据分析师崩溃有多简单?
  7. 【译】在您的应用中安全使用Android的篡改检测 (Using Android's tamper detection securely in your app)
  8. 集成框架比较– Spring集成,Mule ESB或Apache Camel
  9. Java中的weak reference 和 soft reference
  10. 国内APP乱象再现 京东金融APP被质疑窃取用户隐私
  11. 高德地图怎么搜索marker_百度、高德、腾讯地图坐标认证怎么弄,3分钟轻松自助认证...
  12. 世界地图可以无限放大_做外贸有哪些软件可以推荐?
  13. 语法分析器的生成器——Bison
  14. C语言编程100题(基础知识、基本结构、数组)
  15. 接口压力测试数据的完整性校验
  16. uniapp 树组件 可设置展开层级 可设置回显内容 可设置单选多
  17. c语言课程设计作业心得体会,【c语言课程设计心得体会】 c语言课程设计报告总结...
  18. 用Python分析周杰伦歌曲并进行数据可视化
  19. matlab数组下标可为正整数和逻辑数
  20. Javascrit通过百度地图API获取客户端IP、地址

热门文章

  1. 线性代数代码实现(六)矩阵除法(C++)
  2. Web前端学习笔记10:移动web开发流式布局_flex布局
  3. html5把视频作为背景音乐,在手机上看视频,认为视频的背景音乐很好听,可以用什么软件把背景音乐提取出来吗?...
  4. java商圈排序,Jeecg实战-商圈功能实现
  5. The ONE携手郎朗:全球公益钢琴盛典传递快乐音乐教育
  6. 【花雕动手做】有趣好玩的音乐可视化系列项目(32)--P10矩阵LED单元板
  7. <物联网>emqx服务器关闭匿名认证并开启ClientId认证
  8. 2019制冷与空调设备安装修理作业模拟考试系统及模拟题库
  9. SGAME:一个简单的go游戏服务端框架
  10. 徐小明:周四操作策略