1. ListItem基类
const {ccclass, property} = cc._decorator;
@ccclass
export default class ListViewItem extends cc.Component {
itemId: number = 0;
onLoad() {
}
/**
* 不需要重写这个方法
* @param index 索引从0开始
* @param data 所有列表数据
*/
initData(index: number, data: any) {
this.itemId = index;
this.init(index, data);
}
/**
* 重写这个方法
* 要在listview中子item继承这个ListViewItem
* @param index item索引从0开始
* @param data 所有列表数据
*/
init(index: number, data: any) {
}
}
2. ListView 类
import ListViewItem from "./ListViewItem";
const {ccclass, property} = cc._decorator;
/**
* 实现scrollview item循环滚动 ,目前只支持垂直
*/
/**
* count: length of listview
* listViewData: data of listview
*/
type ListViewData = {
count: number,
listViewData: any
}
@ccclass
export default class ListView extends cc.Component {
// @property(cc.Label)
// itemCountLabel: cc.Label = null;
@property({
type: cc.Prefab,
tooltip: "listview单元格,仅支持大小相等的单元格"
})
prefabCell: cc.Prefab = null; // listview单元格,仅支持大小相等的单元格
@property({
type: cc.ScrollView,
tooltip: "找到scrollview的控件"
})
scrollView:cc.ScrollView = null; // 找到scrollview的控件
@property(cc.Integer)
spacing: number = 0; // listview单元格间距
// @property(cc.String)
// cellScriptName: string = "";
@property({
type: cc.Node,
tooltip: "找到scrollview的content"
})
content: cc.Node = null; // 找到scrollview的content, 搭配scrollview使用
@property({
type: cc.Integer,
tooltip: "缓冲区总个数,即上下缓冲区分别bufferZoneCount / 2"
})
bufferZoneCount: number = 4; // 缓冲区总个数,即上下缓冲区分别bufferZoneCount / 2
items: cc.Node[] = [];
itemCount: number = 0;
prefabCellHeight: number = 0;
prefabCellWidth: number = 0;
updateTimer: number = 0;
updateInterval: number = 0.1;
lastContentPosX: number = 0;
lastContentPosY: number = 0;
_bufferZone: number = 0; //when item is away from bufferZone, we relocate it
_spawnCount: number = 0; // how many items we actually spawn
_totalCount: number = 0; // how many items we need for the whole list
_isUpdate: boolean = false;
_itemData: any;
// _listViewItem: ListViewItem = new ListViewItem();
onLoad () {
this._bufferZone = this.scrollView.node.height;
this.content.anchorX = 0.5;
this.content.anchorY = 1;
if (this.scrollView.horizontal) {
this._bufferZone = this.scrollView.node.width;
this.content.anchorX = 0;
this.content.anchorY = 0.5;
}
this.prefabCellHeight = this.prefabCell.data.height;
this.prefabCellWidth = this.prefabCell.data.width;
this.scrollView.node.on('scrolling', this.scrolling.bind(this), this);
this.scrollView.node.on('scroll-ended', this.scrollEnded.bind(this), this);
// this.init({count: 20, listViewData: null});
}
/**
* 初始化ListView组件
* @param listViewData ListViewData
*/
public init (listViewData: ListViewData) {
this.content.destroyAllChildren();
this.items = [];
this.lastContentPosY = 0;
this.lastContentPosX = 0;
this.itemCount = listViewData.count;
this._itemData = listViewData.listViewData;
if (this.scrollView.vertical && this.scrollView.horizontal) {
return;
}
this._totalCount = this.itemCount;
this._spawnCount = Math.floor(this._bufferZone / (this.prefabCellHeight + this.spacing)) + this.bufferZoneCount;
if (this.scrollView.horizontal) {
this._spawnCount = Math.floor(this._bufferZone / (this.prefabCellWidth + this.spacing)) + this.bufferZoneCount;
}
if (this._spawnCount > this._totalCount) {
this._spawnCount = this._totalCount;
}
if (this.scrollView.vertical) {
this.scrollView.scrollToTop();
this.content.height = this._totalCount * (this.prefabCellHeight + this.spacing) + this.spacing; // get total content height
} else if (this.scrollView.horizontal) {
this.scrollView.scrollToLeft();
this.content.width = this._totalCount * (this.prefabCellWidth + this.spacing) + this.spacing; // get total content width
}
for (let i = 0; i < this._spawnCount; ++i) { // spawn items, we only need to do this once
let item = cc.instantiate(this.prefabCell);
this.content.addChild(item);
item.getComponent(ListViewItem).initData(i, this._itemData);
if (this.scrollView.vertical) {
item.setPosition(0, -item.height * (0.5 + i) - this.spacing * (i + 1));
} else if (this.scrollView.horizontal) {
item.setPosition(item.width * (i + 0.5) + this.spacing * (i + 1), 0);
}
this.items.push(item);
}
}
/**
* 获取item在scrollview中的位置
* @param item item位置
*/
private getPositionInView (item) { // get item position in scrollview's node space
let worldPos = item.parent.convertToWorldSpaceAR(item.position);
let viewPos = this.scrollView.node.convertToNodeSpaceAR(worldPos);
return viewPos;
}
/**
* 监听:滚动时更新
*/
private scrolling(){
this._isUpdate = true;
}
/**
* 监听:滚动停止时不执行逻辑
*/
private scrollEnded() {
this._isUpdate = false;
}
update (dt) {
// this.itemCountLabel.string = (this.content.childrenCount).toString();
// scrolling excute or don't excute
if (!this._isUpdate) return;
this.updateTimer += dt;
if (this.updateTimer < this.updateInterval) return; // we don't need to do the math every frame
this.updateTimer = 0;
let items = this.items;
let buffer = this._bufferZone;
if (this.scrollView.vertical) {
let isDown = this.scrollView.content.y < this.lastContentPosY; // scrolling direction
let offset = (this.prefabCellHeight + this.spacing) * items.length;
for (let i = 0; i < items.length; ++i) {
let viewPos = this.getPositionInView(items[i]);
let item = items[i].getComponent(ListViewItem);
if (isDown) {
// if away from buffer zone and not reaching top of content
if (viewPos.y < -buffer && items[i].y + offset < 0) {
items[i].y = items[i].y + offset;
items[i].getComponent(ListViewItem).initData(item.itemId -items.length, this._itemData);
}
} else {
// if away from buffer zone and not reaching bottom of content
if (viewPos.y > buffer / 2 + (this.prefabCellHeight + this.spacing) / 2 && items[i].y - offset > -this.content.height) {
items[i].y = items[i].y - offset;
items[i].getComponent(ListViewItem).initData(item.itemId + items.length, this._itemData);
}
}
}
// update lastContentPosY
this.lastContentPosY = this.scrollView.content.y;
} else if (this.scrollView.horizontal) {
let isRight = this.scrollView.content.x > this.lastContentPosX; // scrolling direction
let offset = (this.prefabCellWidth + this.spacing) * items.length;
for (let i = 0; i < items.length; ++i) {
let viewPos = this.getPositionInView(items[i]);
if (isRight) {
// if away from buffer zone and not reaching top of content
if (viewPos.x > buffer / 2 + (this.prefabCellWidth + this.spacing) / 2 && items[i].x - offset > 0) {
items[i].x = items[i].x - offset;
}
} else {
// if away from buffer zone and not reaching bottom of content
if (viewPos.x < -buffer / 2 - (this.prefabCellWidth + this.spacing) / 2 && items[i].x + offset < this.content.width) {
items[i].x = items[i].x + offset;
}
}
}
// update lastContentPosX
this.lastContentPosX = this.scrollView.content.x;
}
}
};
3. 使用如图
ListView 绑定到 ScrollView节点下,UIFreeGoldItem 为滚动条,其类继承ListIViewitem。