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); } }