443 lines
14 KiB
TypeScript
443 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() {
|
||
// 回收所有节点
|
||
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();
|
||
}
|
||
} |