Files
MatchMaster/assets/Script/module/List/scrollviewList.ts
T
2026-06-17 15:43:40 +08:00

453 lines
14 KiB
TypeScript

import NodePoolMgr from "../NodePool/NodePoolMgr";
// ListLayoutManager.ts
const { ccclass, property } = cc._decorator;
/**
* 列表排列方式
*/
export enum ListLayoutType {
/**垂直排列 */
Vertical = 1,
/**水平排列 */
Horizontal = 2,
/**网格排列 */
Grid = 3
}
/**
* 网格布局方向
*/
export enum GridDirection {
/**水平优先 */
Horizontal = 1,
/**垂直优先 */
Vertical = 2
}
@ccclass
export default class scrollViewList extends cc.Component {
/**NodePool处理器 */
@property({ type: cc.Node, tooltip: "NodePool处理器节点" })
public nodePoolNode: cc.Node = null;
/**列表项预制体 */
@property({ type: cc.Prefab, tooltip: "列表项预制体" })
public itemPrefab: cc.Prefab = null;
/**排列方式 */
@property({ type: cc.Enum(ListLayoutType), tooltip: "排列方式" })
public layoutType: ListLayoutType = ListLayoutType.Vertical;
/**网格布局方向 */
@property({
type: cc.Enum(GridDirection),
tooltip: "网格布局方向",
visible() {
return this.layoutType === ListLayoutType.Grid;
}
})
public gridDirection: GridDirection = GridDirection.Horizontal;
/**列表项之间X间隔 */
@property({ type: cc.Integer, tooltip: "列表项X间隔" })
public spacingX: number = 0;
/**列表项之间Y间隔 */
@property({ type: cc.Integer, tooltip: "列表项Y间隔" })
public spacingY: number = 0;
/**上间距 */
@property({ type: cc.Integer, tooltip: "上间距" })
public paddingTop: number = 0;
/**下间距 */
@property({ type: cc.Integer, tooltip: "下间距" })
public paddingBottom: number = 0;
/**左间距 */
@property({ type: cc.Integer, tooltip: "左间距" })
public paddingLeft: number = 0;
/**右间距 */
@property({ type: cc.Integer, tooltip: "右间距" })
public paddingRight: number = 0;
/**列表项宽度 */
@property({ type: cc.Integer, tooltip: "列表项宽度,-1表示使用预制体原始宽度" })
public itemWidth: number = -1;
/**列表项高度 */
@property({ type: cc.Integer, tooltip: "列表项高度,-1表示使用预制体原始高度" })
public itemHeight: number = -1;
/**是否自动调整content大小 */
@property({ tooltip: "是否自动调整content大小" })
public autoResizeContent: boolean = true;
// 私有属性
private scrollView: cc.ScrollView = null;
private content: cc.Node = null;
private nodePoolMgr: NodePoolMgr = null;
private dataList: any[] = [];
private itemNodes: cc.Node[] = [];
private itemWidthActual: number = 0;
private itemHeightActual: number = 0;
onLoad() {
this.init();
}
/**
* 初始化组件
*/
private init() {
this.scrollView = this.node.getComponent(cc.ScrollView);
if (!this.scrollView) {
console.error("scrollViewList: ScrollView component not found");
return;
}
this.content = this.scrollView.content;
if (!this.content) {
console.error("scrollViewList: Content node not found");
return;
}
// 设置content锚点
this.content.anchorX = 0;
this.content.anchorY = 1;
// 获取NodePoolMgr组件
if (this.nodePoolNode) {
this.nodePoolMgr = this.nodePoolNode.getComponent(NodePoolMgr);
}
if (!this.nodePoolMgr) {
console.error("scrollViewList: NodePoolMgr component not found");
return;
}
}
/**
* 设置数据
* @param data 数据数组
*/
public setData(data: any[]) {
this.dataList = data ? [...data] : [];
this.refreshContent();
}
/**
* 刷新内容显示
*/
private refreshContent() {
if (!this.content || !this.nodePoolMgr || !this.itemPrefab) {
console.warn("scrollViewList: Content, NodePoolMgr or itemPrefab not ready");
return;
}
// 清空现有内容
this.clearContent();
if (this.dataList.length === 0) {
return;
}
// 初始化实际宽高
this.initItemSize();
// 根据排列方式更新content大小
if (this.autoResizeContent) {
this.resizeContent();
}
// 创建列表项
this.createItems();
}
/**
* 初始化列表项实际尺寸
*/
private initItemSize() {
// 如果设置了自定义尺寸,则使用自定义尺寸
if (this.itemWidth > 0) {
this.itemWidthActual = this.itemWidth;
} else {
// 否则使用预制体原始宽度
const tempNode = cc.instantiate(this.itemPrefab);
this.itemWidthActual = tempNode.width;
tempNode.destroy();
}
if (this.itemHeight > 0) {
this.itemHeightActual = this.itemHeight;
} else {
// 否则使用预制体原始高度
const tempNode = cc.instantiate(this.itemPrefab);
this.itemHeightActual = tempNode.height;
tempNode.destroy();
}
}
/**
* 调整content大小
*/
private resizeContent() {
const dataLen = this.dataList.length;
switch (this.layoutType) {
case ListLayoutType.Vertical:
this.content.width = this.content.parent.width;
// 修正content高度计算:确保最后一个项能完全显示
// 计算公式:顶部边距 + 所有项高度 + 间隔总和 + 底部边距
if (dataLen > 0) {
this.content.height = this.paddingTop +
dataLen * this.itemHeightActual +
Math.max(0, dataLen - 1) * this.spacingY +
this.paddingBottom;
} else {
this.content.height = this.paddingTop + this.paddingBottom;
}
if (cc.sys.isMobile) {
// 可以根据需要添加额外的底部填充
this.content.height += 20; // 添加20像素的额外空间
}
break;
case ListLayoutType.Horizontal:
this.content.height = this.content.parent.height;
// 修正content宽度计算
if (dataLen > 0) {
this.content.width = this.paddingLeft +
dataLen * this.itemWidthActual +
Math.max(0, dataLen - 1) * this.spacingX +
this.paddingRight;
} else {
this.content.width = this.paddingLeft + this.paddingRight;
}
break;
case ListLayoutType.Grid:
if (this.gridDirection === GridDirection.Horizontal) {
// 水平优先网格
const rowCount = Math.ceil(dataLen / this.getGridColCount());
if (rowCount > 0) {
this.content.height = this.paddingTop +
rowCount * this.itemHeightActual +
Math.max(0, rowCount - 1) * this.spacingY +
this.paddingBottom;
} else {
this.content.height = this.paddingTop + this.paddingBottom;
}
this.content.width = this.content.parent.width;
} else {
// 垂直优先网格
const colCount = Math.ceil(dataLen / this.getGridRowCount());
if (colCount > 0) {
this.content.width = this.paddingLeft +
colCount * this.itemWidthActual +
Math.max(0, colCount - 1) * this.spacingX +
this.paddingRight;
} else {
this.content.width = this.paddingLeft + this.paddingRight;
}
this.content.height = this.content.parent.height;
}
break;
}
}
/**
* 获取网格列数
*/
private getGridColCount(): number {
if (this.layoutType !== ListLayoutType.Grid) return 1;
const contentWidth = this.content.parent.width - this.paddingLeft - this.paddingRight;
return Math.floor((contentWidth + this.spacingX) / (this.itemWidthActual + this.spacingX));
}
/**
* 获取网格行数
*/
private getGridRowCount(): number {
if (this.layoutType !== ListLayoutType.Grid) return 1;
const contentHeight = this.content.parent.height - this.paddingTop - this.paddingBottom;
return Math.floor((contentHeight + this.spacingY) / (this.itemHeightActual + this.spacingY));
}
/**
* 创建列表项
*/
private createItems() {
const dataLen = this.dataList.length;
for (let i = 0; i < dataLen; i++) {
// 调用NodePoolMgr时传入指定的预制体
const itemNode = this.nodePoolMgr.getItem(this.itemPrefab);
if (!itemNode) {
console.error("scrollViewList: Failed to get item from NodePoolMgr");
continue;
}
itemNode.parent = this.content;
this.itemNodes.push(itemNode);
// 设置位置
this.setPosition(itemNode, i);
// 设置数据
const itemComponent = itemNode.getComponent('ListItem') || itemNode.getComponent(cc.Component);
if (itemComponent && typeof itemComponent['updateItem'] === 'function') {
itemComponent['updateItem'](this.dataList[i], i);
}
}
}
/**
* 设置项的位置
* @param itemNode 项节点
* @param index 索引
*/
private setPosition(itemNode: cc.Node, index: number) {
// 确保节点尺寸正确
if (this.itemWidth > 0) {
itemNode.width = this.itemWidth;
}
if (this.itemHeight > 0) {
itemNode.height = this.itemHeight;
}
switch (this.layoutType) {
case ListLayoutType.Vertical:
// 垂直排列:每个项在垂直方向上按顺序排列
// 修正位置计算,确保能正确显示所有项
const y = - (this.paddingTop + itemNode.height / 2 + index * (itemNode.height + this.spacingY));
itemNode.setPosition(this.paddingLeft + itemNode.width / 2, y);
break;
case ListLayoutType.Horizontal:
// 水平排列:每个项在水平方向上按顺序排列
const x = this.paddingLeft + itemNode.width / 2 + index * (itemNode.width + this.spacingX);
itemNode.setPosition(x, -this.content.height + this.content.height / 2);
break;
case ListLayoutType.Grid:
if (this.gridDirection === GridDirection.Horizontal) {
const col = index % this.getGridColCount();
const row = Math.floor(index / this.getGridColCount());
const gridX = this.paddingLeft + itemNode.width / 2 + col * (itemNode.width + this.spacingX);
const gridY = - (this.paddingTop + itemNode.height / 2 + row * (itemNode.height + this.spacingY));
itemNode.setPosition(gridX, gridY);
} else {
const row = index % this.getGridRowCount();
const col = Math.floor(index / this.getGridRowCount());
const gridX = this.paddingLeft + itemNode.width / 2 + col * (itemNode.width + this.spacingX);
const gridY = - (this.paddingTop + itemNode.height / 2 + row * (itemNode.height + this.spacingY));
itemNode.setPosition(gridX, gridY);
}
break;
}
}
/**
* 清空内容
*/
private clearContent() {
if (this.itemNodes && this.itemNodes.length != null) {
for (let i = 0; i < this.itemNodes.length; i++) {
if (this.nodePoolMgr && this.itemNodes[i]) {
this.nodePoolMgr.putItem(this.itemNodes[i]);
}
}
}
// 回收所有节点
this.itemNodes = [];
if (this.content) {
if (this.content.children && this.content.children.length != null) {
if (this.content.children.length > 0) {
this.content.removeAllChildren();
}
}
}
}
/**
* 添加数据项
* @param data 数据
* @param index 插入位置,默认添加到末尾
*/
public addItem(data: any, index?: number) {
if (index === undefined || index >= this.dataList.length) {
this.dataList.push(data);
} else {
this.dataList.splice(index, 0, data);
}
this.refreshContent();
}
/**
* 删除数据项
* @param index 索引
*/
public removeItem(index: number) {
if (index >= 0 && index < this.dataList.length) {
this.dataList.splice(index, 1);
this.refreshContent();
}
}
/**
* 更新数据项
* @param index 索引
* @param data 新数据
*/
public updateItem(index: number, data: any) {
if (index >= 0 && index < this.dataList.length) {
this.dataList[index] = data;
// 更新对应节点
if (index < this.itemNodes.length) {
const itemComponent = this.itemNodes[index].getComponent('ListItem') ||
this.itemNodes[index].getComponent(cc.Component);
if (itemComponent && typeof itemComponent['updateItem'] === 'function') {
itemComponent['updateItem'](data, index);
}
}
}
}
/**
* 获取数据项
* @param index 索引
*/
public getItemData(index: number): any {
if (index >= 0 && index < this.dataList.length) {
return this.dataList[index];
}
return null;
}
/**
* 获取数据长度
*/
public getDataLength(): number {
return this.dataList.length;
}
/**
* 清空所有数据
*/
public clearData() {
this.dataList = [];
this.refreshContent();
}
/**
* 销毁时清理资源
*/
onDestroy() {
this.clearContent();
}
}