版本: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
原文:https://www.cnblogs.com/gamedaybyday/p/13270209.html