滚动列表在游戏中也很常见,比如排行榜 、充值记录等,在这些场景中,都有共同的特点, 那就是:数据量大 , 结构相同。
在cocoscreator 中,没有现成的 Listview 控件, 无奈之下, 只能自己动手 用ScrollView 来实现一个。这样,有类似需求的朋友,能专注业务功能的开发,就不用重复造轮了。
⚠️ 文末附 ListView.ts 完整源码, 可直接拿去使用。
下面以排行榜Listview 实现为例,进行详细说明。
ListView 实现效果:
ListView 实现原理:
ListView 实现方式,类似 Android的 ListView 。
采用了AbsAdapter 适配器,用于设置数据,更新视图页面,获取数据数量,计算 item 显示位置等。
采用了 ScrollView 配合 item 预制体Prefab 来实现,动态生成列表项, 支持调整 item 项的间距,支持横向和竖向滚动 。
ListView 还设计了简单的上/下拉通知, 只需要初始化时设置相应回调方法即可。
使用步骤:
step 1 ,在creator层级管理器中,新建 ScrollView 节点,并做如下配置:
这里命名为 sore_rank_listview
step 2 ,独立新建一个item 预制体文件
这里命名为:score_rank_item ,添加了以下属性和布局
step 3 ,在层级管理器中,选择score_rank_item 节点,然后在creator属性检查器中,挂载ScoreRankItem.ts 脚本,并做如下属性配置:
step 4 ,在层级管理器中,选择Listview 节点,然后在creator属性检查器中,挂载Listview.ts 脚本,并做如下配置:
参数解释:
- Spacing :用来约定item 之间的间距
- SpawnCount: 用来约定超过可见区域的额外显示项数,可以调整滚动时的平滑性。
- Item Template :独立的item 预制体
- scroollview : 滚动条控件,在这里和 listview 控件是同一个节点
step 5 ,根据排行榜显示内容,我们准备了一个数据结构
export class RankItemData { /** 用户ID */ userid:number; /** 用户昵称 */ nickName:string; /** 排行名次 */ topLevel:number; /** 自定义头像id */ faceid:number; /** VIP */ vipLevel:number; /** 金币 */ score:number; reset(){ this.userid = 0; this.nickName = ''; this.topLevel = 0; this.faceid = 0; this.vipLevel = 0; this.score = 0; } }
step 6 ,我们需要准备数据列表或者是数组
// 离线测试代码 let datas:Array<RankItemData>= new Array<RankItemData>; for(let i=0;i<100;i++) { let itemData:RankItemData = new RankItemData(); itemData.userid = 1000+i; itemData.faceid= 1; itemData.nickName="userName"+i; itemData.topLevel = i+1; itemData.vipLevel = i % 7 + 1; itemData.score = (101 - i)*10000; datas[i] = itemData; }
step 7 ,我们需要一个数据到Item的适配层, ListView 组件类中提供了一个基类AbsAdapter ,我们实现它。
只需要继承此类,重写updateView()函数,对相应索引的itemComponent进行数据设置即可:
class ScoreRankListAdapter extends AbsAdapter { updateView(item:Node, posIndex: number) { let comp = item.getComponent(ScoreRankItemComp); if (comp) { let data = this.getItem(posIndex); comp.setData(this.getItem(posIndex)); } } }
step 8,数据显示和更新
@property(ListView) private scoreRankListView:ListView; private _scoreRankListAdapter: ScoreRankListAdapter | null = null; get scoreRankListAdapter(): ScoreRankListAdapter { if (!this._scoreRankListAdapter) { this._scoreRankListAdapter = new ScoreRankListAdapter(); } return this._scoreRankListAdapter; } this.scoreRankListAdapter.setDataSet(args); this.scoreRankListView.setAdapter(this.scoreRankListAdapter);
step 9、ScoreRankItem.ts 源码
import { _decorator,Component,Label, Sprite} from "cc"; const { ccclass, property } = _decorator; @ccclass export class ScoreRankItem extends Component { @property(Label) private labelLevel!:Label; @property(Sprite) private spriteAvatr!:Sprite; @property(Label) private lableNickName!:Label; @property(Label) private labelVip!:Label; @property(Label) private labelScore!:Label; @property(Sprite) private spriteLevel1!:Sprite; @property(Sprite) private spriteLevel2!:Sprite; @property(Sprite) private spriteLevel3!:Sprite; public setData(data: any) { const itemData = data as RankItemData; this.lableNickName.string = itemData.nickName; this.labelVip.string = "VIP " + String(itemData.vipLevel); this.labelScore.string = String(itemData.score); ... } }