版本:2.3.4
cocos没有List组件,所以要自己写。
从cocos的example项目中找到listView的demo来改造
新修改的ListView对比原来有以下改动:
1. 去掉了totalCount、spawnCount和bufferZone的计算,根据实际情况自动计算合适的值。
2. 增加了列表项数据的传入和刷新。例如排行榜做列表,可以传入排行榜数据[{rank:1,name:"啊",{rank:2,name:"啦"},...}]来显示。
3. 将ListViewCtrl和ScrollView两个Node,合成一个Node,做成prefab,并拖动到自定义控件区域,以备复用。
4. 去掉了addItem和removeItem,列表项由传入的data决定。
5. 增加了ListItem类来处理data数据的显示。
UI结构如下图:
对比cocos example的,ListView所有UI在一个节点上,方便做成预制件。
ListView类:
import ListItem from "./ListItem"; const {ccclass, property} = cc._decorator; /** * 列表 * 根据cocos_example的listView改动而来 * @author chenkai 2020.7.8 */ @ccclass export default class ListView extends cc.Component { /**列表选项 */ @property(cc.Node) public item:cc.Node = null; /**列表选项类 */ @property(cc.String) public itemClass:string = ""; /**列表滚动容器 */ @property(cc.ScrollView) public scrollView:cc.ScrollView = null; /**列表项之间间隔 */ @property(cc.Integer) public spacing:number = 0; /**列表项实例数量 */ private spawnCount:number = 0; /**距离scrollView中心点的距离,超过这个距离的item会被重置,一般设置为 scrollVIew.height/2 + item.heigt/2 + spaceing,因为这个距离item正好超出scrollView显示范围 */ private bufferZone:number = 0; /**列表项总数 */ public totalCount:number = 0; /**scrollView的内容容器 */ private content:cc.Node = null; /**存放列表项实例的数组 */ private items:Array<cc.Node> = []; /**刷新列表计时 */ private updateTimer:number = 0; /**刷新列表间隔 */ private updateInterval:number = 0; /**上一次content的Y值,用于和现在content的Y值比较,得出是向上还是向下滚动 */ private lastContentPosY:number = 0; /**列表项数据 */ private itemDataList:any = []; /**item的高度 */ private itemHeight:number = 0; onLoad() { //初始化 this.content = this.scrollView.content; this.items = []; this.updateTimer = 0; this.updateInterval = 0.1; this.lastContentPosY = 0; this.itemHeight = this.item.height; this.content.removeAllChildren(); //计算创建的item实例数量,比当前scrollView容器能放下的item数量再加上2个 this.spawnCount = Math.round(this.scrollView.node.height/( this.itemHeight + this.spacing)) + 2; //计算bufferZone this.bufferZone = this.scrollView.node.height/2 + this.itemHeight/2 + this.spacing; //暂停滚动 this.enabled = false; this.scrollView.enabled = false; } /** * 设置item的数据 * @example * setData([{id:1,msg:"a"},{id:2,msg:"b"}]) * @param itemDataList item数据列表 */ public setData(itemDataList:any){ //复制item数据,如果item数据源改变,则需要重新setData一次来显示新数据 this.itemDataList = itemDataList.slice(); this.totalCount = this.itemDataList.length; this.createItem(); //运行滚动 this.enabled = true; this.scrollView.enabled = true; } /**创建item实例 */ private createItem () { this.content.height = this.totalCount * ( this.itemHeight + this.spacing) + this.spacing; this.clearAllItem(); let len = this.totalCount < this.spawnCount?this.totalCount:this.spawnCount; for (let i = 0; i < len; i++) { // spawn items, we only need to do this once let item = cc.instantiate(this.item); this.content.addChild(item); item.setPosition(0, -item.height * (0.5 + i) - this.spacing * (i + 1)); item.getComponent(this.itemClass).updateItem(i, this.itemDataList[i]); this.items.push(item); } } /**清理item实例 */ private clearAllItem(){ for(let i=0,len=this.items.length;i<len;i++){ let item = this.items[i]; item.destroy(); } this.items.length = 0; } /**获取item在scrollView的局部坐标 */ private getPositionInView(item) { let worldPos = item.parent.convertToWorldSpaceAR(item.position); let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos); return viewPos; } update(dt) { this.updateTimer += dt; if (this.updateTimer < this.updateInterval) return; this.updateTimer = 0; let items = this.items; let buffer = this.bufferZone; let isDown = this.scrollView.content.y < this.lastContentPosY; // scrolling direction let offset = ( this.itemHeight + 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 content if (viewPos.y < -buffer && items[i].y + offset < 0) { //console.log("更新A前,items[i]:" + i +"viewPos.y:",viewPos.y,"buffer:" ,buffer,"items[i].y:", items[i].y,"offset:",offset,"this.content.height:",this.content.height); items[i].y = items[i].y + offset; let item = items[i].getComponent(this.itemClass); let itemId = item.itemID - items.length; // update item id //item.updateItem(itemId); item.updateItem(itemId,this.itemDataList[itemId]); //console.log("更新A后,tmpID:",item.tmplID,"itemId:" ,itemId,"viewPosY:", viewPos.y,"buffer:",buffer,"offset:",offset); } } else { // if away from buffer zone and not reaching bottom of content if (viewPos.y > buffer && items[i].y - offset > -this.content.height) { //console.log("更新B前,items[i]:" + i +"viewPos.y:",viewPos.y,"buffer:" ,buffer,"items[i].y:", items[i].y,"offset:",offset,"this.content.height:",this.content.height); items[i].y = items[i].y - offset; let item = items[i].getComponent(this.itemClass); let itemId = item.itemID + items.length; //item.updateItem(itemId); item.updateItem(itemId,this.itemDataList[itemId]); //console.log("更新B后,tmpID:",item.tmplID,"itemId:" ,itemId,"viewPosY:", viewPos.y,"items[i].y:",items[i].y,"buffer:",buffer,"offset:",offset); } } } // update lastContentPosY this.lastContentPosY = this.scrollView.content.y; } /** * 滚动到指定位置 * @param vec2 位置 */ public scrollToFixedPosition (vec2:cc.Vec2) { this.scrollView.scrollToOffset(vec2, 2); } /**销毁 */ public onDestroy(){ } }
ListItem类:
const {ccclass, property} = cc._decorator; /** * 列表项基类 * @author chenkai 2020.7.8 */ @ccclass export default class ListItem extends cc.Component { /**当前项ID,0表示第一项 */ public itemID:number = 0; /**数据 */ public data:any; /** * 刷新 * @param itemID 当前项ID * @param data 数据 */ public updateItem(itemID, data) { this.itemID = itemID; this.data = data; this.dataChanged(); } /**数据改变 */ protected dataChanged(){ } }
RankListItem类:
继承自ListItem,并重写了dataChanged方法,在Item上下滚动导致刷新时,重置文本。
import ListItem from "./ListView/ListItem"; const {ccclass, property} = cc._decorator; @ccclass export default class RankListItem extends ListItem { private rankLab:cc.Label; private nameLab:cc.Label; onLoad(){ this.rankLab = cc.find("rankLab",this.node).getComponent(cc.Label); this.nameLab = cc.find("nameLab",this.node).getComponent(cc.Label); } protected dataChanged(){ this.rankLab.string = "第" + this.data.rank; this.nameLab.string = this.data.name; } }
HelloWorld代码中使用
import ListView from "./ListView/ListView"; const {ccclass, property} = cc._decorator; @ccclass export default class Helloworld extends cc.Component { //排行榜 private rankListView:ListView; onLoad(){ } start(){ //获取排行榜ListView this.rankListView = cc.find("RankListView", this.node).getComponent(ListView); //设置排行榜数据 let rankData = [{rank:1,name:"啊飞"},{rank:2,name:"肚肚"},{rank:3,name:"普洱"}, {rank:4,name:"电饭锅"},{rank:5,name:"松岛"},{rank:6,name:"撒嗄"}]; this.rankListView.setData(rankData); //重新设置排行榜数据 rankData = [{rank:1,name:"啊飞"},{rank:2,name:"肚肚"},{rank:3,name:"普洱"}, {rank:4,name:"电饭锅"},{rank:5,name:"松岛"},{rank:6,name:"撒嗄"}, {rank:7,name:"打手犯规"},{rank:8,name:"萨达"},{rank:9,name:"苟富贵"}]; this.rankListView.setData(rankData); //销毁 //this.rankListView.node.destroy(); } }
实际效果:
Demo:
https://files-cdn.cnblogs.com/files/gamedaybyday/ListViewDemo.7z