Files
slgclient/assets/scripts/utils/ListLogic.ts
2025-11-18 18:38:53 +08:00

467 lines
16 KiB
TypeScript

import { _decorator, Component, ScrollView, Prefab, Node, NodePool, EventHandler, UITransform, instantiate, CCBoolean, CCString, Vec3 } from 'cc';
const {ccclass, property} = _decorator;
@ccclass('ListLogic')
export default class ListLogic extends Component {
@property(ScrollView)
scrollView: ScrollView = null;
@property(Prefab)
itemPrefab: Prefab = null;
@property(Node)
itemNode: Node = null;
@property(CCString)
itemLogicScriptName:string = "";
@property(CCBoolean)
isHorizontal:boolean = false;
@property
columnCount = 1;
@property(CCBoolean)
autoColumnCount:boolean = false;
@property
spaceColumn = 1;
@property
spaceRow = 1;
@property
updateInterval = 0.1;
@property
scale = 1;
@property([EventHandler])
itemClickEvents:EventHandler[] = [];
@property(CCBoolean)
isVirtual:boolean = false;
private _curOffset:number = 0;
private _maxOffset:number = 0;
private _startIndex:number = 0;
private _itemCount:number = 0;
private _updateTimer:number = 0;
private _curIndex:number = 0;
private _newOffset:number = 0;
private _initContentPos:number = 0;
private _maxRowColSize:number = 0;
private _itemWidth:number = 0;
private _itemHeight:number = 0;
private _isUpdateList:boolean = false;
private _itemPool:NodePool = null;
private _items:any = [];
private _datas:any = null;
protected onLoad():void{
this._updateTimer = 0;//上次更新间隔时间
this._curIndex = -1;
this._newOffset = 0;
this._initContentPos = 0;
this._maxRowColSize = 0;//当前一行或者一列可以显示的最大宽度或者高度
this._itemWidth = this._itemHeight = 0;
if (this.itemPrefab) {
this._itemWidth = this.itemPrefab.data.getComponent(UITransform).width * this.scale;//item宽度
this._itemHeight = this.itemPrefab.data.getComponent(UITransform).height * this.scale;//item高度
} else if (this.itemNode) {
this.itemNode.active = false;
this._itemWidth = this.itemNode.getComponent(UITransform).width * this.scale;//item宽度
this._itemHeight = this.itemNode.getComponent(UITransform).height * this.scale;//item高度
}
if (this.isHorizontal) {
this.scrollView.content.getComponent(UITransform).anchorX = 0;
} else {
this.scrollView.content.getComponent(UITransform).anchorY = 1;
}
this._isUpdateList = false;//是否正在更新列表
this._itemPool = new NodePool();//item缓存对象池
this._items = [];//item列表
this.updateList();
}
protected onDestroy():void {
this._itemPool.clear();
this._items.length = 0;
this._datas = null;
}
protected update (dt):void {
this._updateTimer += dt;
if (this._updateTimer < this.updateInterval) {
return;//更新间隔太短
}
this._updateTimer = 0;
// if (this.isVirtual == false) {
// return;//非虚拟列表 不需要刷新位置和数据
// }
if (this._isUpdateList) {
return;//正在重新构建列表的时候 是不刷新的
}
let curOffset = 0;
if (this.isHorizontal) {
curOffset = this._initContentPos - this.scrollView.content.position.x;
} else {
curOffset = this.scrollView.content.position.y - this._initContentPos;
}
curOffset = Math.max(Math.min(curOffset, this._maxOffset), 0);
this.setCurOffset(curOffset);
}
protected setCurOffset(curOffset):void {
if (this._datas == null || this._datas.length == 0) {
return;//没有数据不执行刷新
}
if (this._items == null || this._items.length == 0) {
return;//没有显示对象也不执行刷新
}
if (this._curOffset != curOffset) {
// console.log("setCurOffset", this._curOffset, curOffset);
this._curOffset = curOffset;
if (this.isVirtual) {
if (this.isHorizontal) {
var startIndex = Math.floor(this._curOffset / (this._itemWidth + this.spaceColumn)) * this.columnCount;
this.setStartIndex(startIndex);
} else {
var startIndex = Math.floor(this._curOffset / (this._itemHeight + this.spaceRow)) * this.columnCount;
this.setStartIndex(startIndex);
}
} else {
this.setStartIndex(0);//非虚拟列表startIndex不变
}
//console.log("updatelist11 y", this.scrollView.content.y);
}
}
protected setStartIndex(index) {
if (this._startIndex != index && this._items.length > 0) {
//console.log("setStartIndex", this._startIndex, index);
this._startIndex = index;
let suit = this.scrollView.content.getComponent(UITransform);
for (var i = 0; i < this._items.length; i++) {
var item:Node = this._items[i];
var index1 = this._startIndex + i;
let iuit = item.getComponent(UITransform);
let pos = item.position.clone();
if (this.isHorizontal) {
let _row = i % this.columnCount;
let _toY = _row * (this._itemHeight + this.spaceRow) + iuit.anchorY * this._itemHeight - suit.height * suit.anchorY;
pos.y = -_toY - (suit.height - this._maxRowColSize) / 2;
pos.x = Math.floor(index1 / this.columnCount) * (this._itemWidth + this.spaceColumn) + this.spaceColumn + (1 - iuit.anchorX) * this._itemWidth;
} else {
let _col = i % this.columnCount;
let _toX = _col * (this._itemWidth + this.spaceColumn) + iuit.anchorX * this._itemWidth - suit.width * suit.anchorX;
pos.x = _toX + (suit.width - this._maxRowColSize) / 2;
pos.y = -Math.floor(index1 / this.columnCount) * (this._itemHeight + this.spaceRow) - this.spaceRow - (1 - iuit.anchorY) * this._itemHeight;
}
item.itemIdx = index1;
item.setPosition(pos);
//console.log("update item position x: " + item.x + ", y: " + item.y);
}
this.updateItems();
}
}
/**设置item实例数量*/
protected updateItemCount(count):boolean {
if (this._itemCount != count) {
this._itemCount = count;
//清空列表
var children = this.scrollView.content.children.slice();
this.scrollView.content.removeAllChildren();
for (var i = 0; i < children.length; i++) {
let item = children[i];
if (item.isValid) {
item.off(Node.EventType.TOUCH_END, this.onItemClick, this);
this._itemPool.put(item);//加入对象池
}
}
this._items.length = 0;
for (var i = 0; i < this._itemCount; i++) {
let item = this.createItem();
item.active = false;
item.itemIdx = i;//在item上纪录当前下标
item.on(Node.EventType.TOUCH_END, this.onItemClick, this);
this.scrollView.content.addChild(item);
this._items.push(item);
}
return true;
}
return false;
}
/**
* 更新列表
*/
protected updateList():void {
if (this._datas == null || this._items == null || this._itemPool == null) {
return;
}
//计算布局
if (this._itemWidth <= 0 || this._itemHeight <= 0) {
console.log("the list item has no width or height");
return;
}
if (this._datas.length <= 0) {
this._curOffset = this._startIndex = -1;//重置纪录
this.hideItems();
return;
}
this._isUpdateList = true;
this.scrollView.stopAutoScroll();//更新时 停止滚动
var rowCount = 1;
var showCount = 1;
var dataLen = this._datas.length;
let uit = this.scrollView.content.parent.getComponent(UITransform);
let cuit = this.scrollView.content.getComponent(UITransform);
if (this.isHorizontal) {
if (this.autoColumnCount) {
//自动排列
this.columnCount = Math.floor(uit.height / this._itemHeight);
}
if (this.columnCount < 1) {
this.columnCount = 1;
}
this._maxRowColSize = this.columnCount * (this._itemHeight + this.spaceRow) - this.spaceRow;
rowCount = Math.ceil(uit.width / (this._itemWidth + this.spaceColumn)) + 1;
if (this.isVirtual) {
showCount = rowCount * this.columnCount;
} else {
showCount = dataLen;
}
cuit.width = Math.ceil(dataLen / this.columnCount) * (this._itemWidth + this.spaceColumn);
this._maxOffset = this.scrollView.getMaxScrollOffset().x;
this._initContentPos = uit.width * (0 - uit.anchorX);
} else {
if (this.autoColumnCount) {
//自动排列
this.columnCount = Math.floor(uit.width / this._itemWidth);
}
if (this.columnCount < 1) {
this.columnCount = 1;
}
this._maxRowColSize = this.columnCount * (this._itemWidth + this.spaceColumn) - this.spaceColumn;
rowCount = Math.ceil(uit.height / (this._itemHeight + this.spaceRow)) + 1;
if (this.isVirtual) {
showCount = rowCount * this.columnCount;
} else {
showCount = dataLen;
}
cuit.height = Math.ceil(dataLen / this.columnCount) * (this._itemHeight + this.spaceRow);
this._maxOffset = this.scrollView.getMaxScrollOffset().y;
this._initContentPos = uit.height * (1 - uit.anchorY);
}
var isItemChange = this.updateItemCount(showCount);
this._newOffset = Math.max(Math.min(this._newOffset, this._maxOffset), 0);
if ((isItemChange || this._newOffset != this._curOffset)) {
let pos = this.scrollView.content.position.clone();
this._curOffset = this._newOffset;
if (this.isHorizontal) {
pos.x = -Math.abs(this._initContentPos - this._newOffset);
} else {
pos.y = Math.abs(this._initContentPos + this._newOffset);
}
this._curOffset = -1;//重置纪录
this._startIndex = -1;//重置纪录
this.setCurOffset(this._newOffset);
this.scrollView.content.setPosition(pos);
} else {
this.updateItems();
}
this._isUpdateList = false;
//console.log("updatelist y", this.scrollView.content.y);
console.log("this.scrollView:", this.scrollView);
}
//刷新所有item数据
protected updateItems():void {
for (var i = 0; i < this._items.length; i++) {
var item = this._items[i];
console.log("updateItems:", item, item.itemIdx, this._datas.length, item.itemIdx < this._datas.length)
item.active = item.itemIdx < this._datas.length;
if (item.active) {
this.updateItem(item, item.itemIdx);
this.selectItem(item, item.itemIdx == this._curIndex);
}
//console.log("update item i: " + item.itemIdx + ", active: " + item.active);
}
}
protected hideItems():void {
for (var i = 0; i < this._items.length; i++) {
this._items[i].active = false;
}
}
protected updateItem(item, index):void {
var comp = null;
if (this.itemLogicScriptName) {
comp = item.getComponent(this.itemLogicScriptName);
if (comp && comp.updateItem) {
comp.updateItem(this._datas[index], index);
}
}
}
/**
* 根据下标获取item对象
*/
protected getItem(index):any{
var item = null;
if (this._items) {
for (var i = 0; i < this._items.length; i++) {
if (this._items[i].itemIdx == index) {
item = this._items[i];
break;
}
}
}
return item;
}
/**
* 选中item
*/
protected selectItem(item, isSelected):void {
var comp = null;
if (this.itemLogicScriptName) {
comp = item.getComponent(this.itemLogicScriptName);
if (comp && comp.isSelected) {
comp.isSelected(isSelected);
}
}
}
/**
* 创建item
*/
protected createItem():any {
var item = null;
if (this._itemPool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
item = this._itemPool.get();
} else if (this.itemPrefab) { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 instantiate 重新创建
item = instantiate(this.itemPrefab);
} else if (this.itemNode) { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 instantiate 重新创建
item = instantiate(this.itemNode);
}
item.scale = new Vec3(this.scale, this.scale, this.scale);
item.acitve = true;
item.on(Node.EventType.TOUCH_END, this.onItemClick, this);
return item;
}
protected setIndex(index):void {
if (this._curIndex != index) {
if (this._curIndex >= 0 && this._curIndex < this._datas.length) {
var oldItem = this.getItem(this._curIndex);
if (oldItem) {
this.selectItem(oldItem, false);
}
}
var newItem = this.getItem(index);
if (newItem) {
this.selectItem(newItem, true);
}
this._curIndex = index;
}
}
/**
* item点击回调
*/
protected onItemClick(event):void {
var index = event.target.itemIdx;
this.setIndex(index);
this.itemClickEvents.forEach(function (handler) {
handler.emit([this._datas[index], index, event.target]);
}.bind(this));
}
/**
* 设置列表数据
* scrollOffset 没有传值代表刷新到初始位置 其他整数代表刷新到当前位置的相对偏移量
*/
public setData(data, scrollOffset?:any):void{
this._datas = data;
if (scrollOffset != null && scrollOffset != undefined && !isNaN(scrollOffset)) {
this._newOffset = this._curOffset + scrollOffset;
} else {
this._newOffset = 0;
}
// console.log("list logiv setData", data, scrollOffset, this._newOffset);
this.updateList();
}
protected scrollToIndex(index):void {
if (this._datas == null || this._items == null || this._itemPool == null) {
return;
}
if (this._isUpdateList) {
return;//正在重新构建列表的时候 是不刷新的
}
if (index < 0 || index >= this._datas.length) {
return;//数据不合法
}
var curOffset = 0;
if (this.isHorizontal) {
curOffset = Math.ceil(index / this.columnCount) * (this._itemWidth + this.spaceColumn);
} else {
curOffset = Math.ceil(index / this.columnCount) * (this._itemHeight + this.spaceRow);
}
curOffset = Math.max(Math.min(curOffset, this._maxOffset), 0);
this.setCurOffset(curOffset);
}
}