cocos creator 滚动列表 ListView
滚动列表,这种东西在游戏中很常见.而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相关推荐
- 《Cocos Creator游戏实战》滚动数字
滚动数字 创建节点 滚动原理 编写脚本 该功能已收录在Many Widgets插件中,使用Cocos Creator 3.x版本的小伙伴可以用该插件快速生成滚动数字. 插件地址:https://sto ...
- Cocos Creator学习目录
目录 安装和启动 文件结构 编辑器基础 基本概念 (场景树 节点 坐标 组件 ) Cocos Creator 脚本简介 Cocos Creator调试 节点 cc.Node 组件开发cc.Compon ...
- cocos creator 学习随笔 day03 节点和组件属性
目录 节点本身属性 控件属性 场景 空节点 3D对象 2D对象 UI组件 光线 特效 摄像机 地形 节点本身属性 第一栏为节点名,可修改,前面得勾,是表示是否使用该节点,去掉代表隐藏该节点,但是该节点 ...
- Cocos Creator v1.5发布:物理集成、2D摄像机、TypeScript
经过1个多月的Beta版发布和社区测试,直至跳票两周之后,我们终于能够发布 Cocos Creator v1.5 正式版了.这个版本又给大家带来了很多实用的新功能. Cocos Creator 作为第 ...
- 游戏滚动列表的优化(降低drawcall从154降低到14,图片大小,界面逻辑)
前因: 领导对于商城按钮点击切换卡顿非常不满,趁这次商城修改,顺便研究一下优化方案. 观察: 打开cocoscreator show FPS按钮,可以看到一些端倪. 第一眼看到drawcall比较高: ...
- Cocos Creator |《飞刀大乱斗》开发教程
本篇文章转载自公众号[一枚小工],作者:一枚小工 本篇文章为大家带来 Cocos Creator 飞刀大乱斗开发系列教程. one 1 主页下方列表选项如何实现 预览效果 一.具体内容 游戏开始后,加 ...
- Cocos Creator 2.0.1 正式发布
2.0.0 是一个经历了大量底层重构的全新版本,部分开发者在升级项目过程中遇到了一些恼人的问题,这些问题我们一一收集并尽全力解决,现在为大家带来 2.0.1 版本.这个版本除了大幅度提升稳定性和项目升 ...
- 用Vue来实现音乐播放器(十六):滚动列表的实现
滚动列表是一个基础组件 他是基于scroll组件实现的 在base文件夹下面创建一个list-view文件夹 里面有list-view.vue组件 <template><!-- 当 ...
- Cocos Creator快速开通联网服务教程
继集成Egret编辑器工作流后,在最新的Cocos Creator v2.0.7 版本中, Creator服务面板也集成了游戏服务器引擎Matchvs的联网服务.现附上开通教程,方便大家更快上手. 1 ...
最新文章
- 一个考察函数基础知识的题
- C/C++动态二维数组的内存分配和释放
- NVIDIA团队:利用神经网络生成极慢视频
- windows 关闭端口被占用脚本
- 测试晶面间距软件_超逼真动图解析常用15大分析测试仪器,必收藏!SEM, 红外,紫外,核磁,质谱,TEM,ICP等...
- 简短的python金融数据分析师_让一位数据分析师崩溃有多简单?
- 【译】在您的应用中安全使用Android的篡改检测 (Using Android's tamper detection securely in your app)
- 集成框架比较– Spring集成,Mule ESB或Apache Camel
- Java中的weak reference 和 soft reference
- 国内APP乱象再现 京东金融APP被质疑窃取用户隐私
- 高德地图怎么搜索marker_百度、高德、腾讯地图坐标认证怎么弄,3分钟轻松自助认证...
- 世界地图可以无限放大_做外贸有哪些软件可以推荐?
- 语法分析器的生成器——Bison
- C语言编程100题(基础知识、基本结构、数组)
- 接口压力测试数据的完整性校验
- uniapp 树组件 可设置展开层级 可设置回显内容 可设置单选多
- c语言课程设计作业心得体会,【c语言课程设计心得体会】 c语言课程设计报告总结...
- 用Python分析周杰伦歌曲并进行数据可视化
- matlab数组下标可为正整数和逻辑数
- Javascrit通过百度地图API获取客户端IP、地址
热门文章
- 线性代数代码实现(六)矩阵除法(C++)
- Web前端学习笔记10:移动web开发流式布局_flex布局
- html5把视频作为背景音乐,在手机上看视频,认为视频的背景音乐很好听,可以用什么软件把背景音乐提取出来吗?...
- java商圈排序,Jeecg实战-商圈功能实现
- The ONE携手郎朗:全球公益钢琴盛典传递快乐音乐教育
- 【花雕动手做】有趣好玩的音乐可视化系列项目(32)--P10矩阵LED单元板
- <物联网>emqx服务器关闭匿名认证并开启ClientId认证
- 2019制冷与空调设备安装修理作业模拟考试系统及模拟题库
- SGAME:一个简单的go游戏服务端框架
- 徐小明:周四操作策略