cb/assets/Script/module/List/scrollviewList.ts
2025-11-11 10:35:05 +08:00

443 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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() {
// 回收所有节点
for (let i = 0; i < this.itemNodes.length; i++) {
this.nodePoolMgr.putItem(this.itemNodes[i]);
}
this.itemNodes = [];
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();
}
}