添加一个表格组件,原理上贴近现代表格系统,理论上可以应对超大型表格以及离散数据集合。
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个数据块——保存 BlockSizeX × BlockSizeY 个单元格数据。
|
||||
/// 数据使用线性数组存储,索引 = localRow * BlockSizeX + localCol。
|
||||
/// </summary>
|
||||
public class XTableBlock
|
||||
{
|
||||
/// <summary>块在全局块坐标系中的行号</summary>
|
||||
public int BlockRow;
|
||||
|
||||
/// <summary>块在全局块坐标系中的列号</summary>
|
||||
public int BlockCol;
|
||||
|
||||
/// <summary>块内单元格列数</summary>
|
||||
public int BlockSizeX;
|
||||
|
||||
/// <summary>块内单元格行数</summary>
|
||||
public int BlockSizeY;
|
||||
|
||||
/// <summary>
|
||||
/// 数据存储——线性数组。
|
||||
/// 索引 = localRow * BlockSizeX + localCol。
|
||||
/// </summary>
|
||||
public XTableCellData[] Cells;
|
||||
|
||||
/// <summary>合并单元格描述列表</summary>
|
||||
public List<XTableMergeDescriptor> MergeDescriptors;
|
||||
|
||||
/// <summary>
|
||||
/// 创建指定尺寸的块
|
||||
/// </summary>
|
||||
public XTableBlock(int blockSizeX, int blockSizeY)
|
||||
{
|
||||
BlockSizeX = blockSizeX;
|
||||
BlockSizeY = blockSizeY;
|
||||
Cells = new XTableCellData[blockSizeX * blockSizeY];
|
||||
MergeDescriptors = new List<XTableMergeDescriptor>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建指定尺寸和位置的块
|
||||
/// </summary>
|
||||
public XTableBlock(int blockSizeX, int blockSizeY, int blockRow, int blockCol)
|
||||
: this(blockSizeX, blockSizeY)
|
||||
{
|
||||
BlockRow = blockRow;
|
||||
BlockCol = blockCol;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过块内局部坐标获取单元格数据
|
||||
/// </summary>
|
||||
public XTableCellData GetCell(int localRow, int localCol)
|
||||
{
|
||||
int index = localRow * BlockSizeX + localCol;
|
||||
if (index < 0 || index >= Cells.Length) return null;
|
||||
return Cells[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过块内局部坐标设置单元格数据
|
||||
/// </summary>
|
||||
public void SetCell(int localRow, int localCol, XTableCellData data)
|
||||
{
|
||||
int index = localRow * BlockSizeX + localCol;
|
||||
if (index < 0 || index >= Cells.Length) return;
|
||||
Cells[index] = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有合并描述重定向此单元格
|
||||
/// </summary>
|
||||
/// <returns>合并描述,若无重定向则返回 null</returns>
|
||||
public XTableMergeDescriptor GetMergeRedirect(int localRow, int localCol)
|
||||
{
|
||||
if (MergeDescriptors == null || MergeDescriptors.Count == 0)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < MergeDescriptors.Count; i++)
|
||||
{
|
||||
if (MergeDescriptors[i].Contains(localRow, localCol))
|
||||
return MergeDescriptors[i];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa9a14d1a32e7fe4f95c29a0c18deddd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 单元格数据模型——仅保存数据内容,不保存自身尺寸。
|
||||
/// 尺寸由渲染阶段的行列标题(行高/列宽)决定。
|
||||
/// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class XTableCellData
|
||||
{
|
||||
/// <summary>文本内容</summary>
|
||||
public string Text;
|
||||
|
||||
/// <summary>图片纹理</summary>
|
||||
public Texture2D Image;
|
||||
|
||||
/// <summary>预制体对象(用于嵌入复杂 UI 元素)</summary>
|
||||
public GameObject Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// 样式命名空间——对应 StyleManager 中的命名空间。
|
||||
/// 为空或 "xeric_table_default" 时使用默认表格样式。
|
||||
/// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。
|
||||
/// </summary>
|
||||
public string StyleNamespace;
|
||||
|
||||
public XTableCellData() { }
|
||||
|
||||
public XTableCellData(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
|
||||
public XTableCellData(string text, string styleNamespace = null) : this(text)
|
||||
{
|
||||
StyleNamespace = styleNamespace;
|
||||
}
|
||||
|
||||
public XTableCellData(string text, Texture2D image, string styleNamespace = null) : this(text, styleNamespace)
|
||||
{
|
||||
Image = image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e783197e3dfc5a5489b99e27504e9cf3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,247 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using XericUI.XTable.Mapping;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格数据主类——持有块字典,提供单元格级别的读写接口。
|
||||
/// 块按需创建(SetCell 时自动创建不存在的块),通过 Z 曲线索引映射。
|
||||
/// </summary>
|
||||
public class XTableData
|
||||
{
|
||||
#region 属性
|
||||
|
||||
/// <summary>块内列数(默认 32)</summary>
|
||||
public int BlockSizeX { get; private set; }
|
||||
|
||||
/// <summary>块内行数(默认 32)</summary>
|
||||
public int BlockSizeY { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 块字典——key = Z 曲线索引(ulong),value = 数据块。
|
||||
/// 具有哈希表性质,按需创建,键不连续(离散存储)。
|
||||
/// </summary>
|
||||
public Dictionary<ulong, XTableBlock> BlockMap { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数
|
||||
|
||||
/// <summary>
|
||||
/// 创建表格数据实例
|
||||
/// </summary>
|
||||
/// <param name="blockSizeX">块内列数,默认 32</param>
|
||||
/// <param name="blockSizeY">块内行数,默认 32</param>
|
||||
public XTableData(int blockSizeX = 32, int blockSizeY = 32)
|
||||
{
|
||||
BlockSizeX = blockSizeX > 0 ? blockSizeX : 32;
|
||||
BlockSizeY = blockSizeY > 0 ? blockSizeY : 32;
|
||||
BlockMap = new Dictionary<ulong, XTableBlock>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 核心访问方法
|
||||
|
||||
/// <summary>
|
||||
/// 通过全局行列坐标获取单元格数据。
|
||||
/// 先计算块坐标和 Z 索引,查找块字典,再检查合并重定向。
|
||||
/// </summary>
|
||||
/// <returns>单元格数据,不存在则返回 null</returns>
|
||||
public XTableCellData GetCell(int row, int col)
|
||||
{
|
||||
XTableCoordinateUtility.CellToBlockCoordinate(
|
||||
row, col, BlockSizeX, BlockSizeY,
|
||||
out int blockRow, out int blockCol,
|
||||
out int localRow, out int localCol);
|
||||
|
||||
ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol);
|
||||
|
||||
if (!BlockMap.TryGetValue(zIndex, out XTableBlock block))
|
||||
return null;
|
||||
|
||||
// 检查合并重定向
|
||||
XTableMergeDescriptor merge = block.GetMergeRedirect(localRow, localCol);
|
||||
if (merge != null)
|
||||
{
|
||||
merge.GetRedirectTarget(out ulong targetBlockIndex, out int targetRow, out int targetCol);
|
||||
|
||||
if (merge.IsCrossBlock)
|
||||
{
|
||||
// 跨块引用:跳转到目标块
|
||||
if (!BlockMap.TryGetValue(targetBlockIndex, out XTableBlock targetBlock))
|
||||
return null;
|
||||
return targetBlock.GetCell(targetRow, targetCol);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 本地引用:同块内重定向
|
||||
return block.GetCell(targetRow, targetCol);
|
||||
}
|
||||
}
|
||||
|
||||
return block.GetCell(localRow, localCol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取单元格数据
|
||||
/// </summary>
|
||||
public bool TryGetCell(int row, int col, out XTableCellData data)
|
||||
{
|
||||
data = GetCell(row, col);
|
||||
return data != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过全局行列坐标设置单元格数据。
|
||||
/// 如果对应的块不存在,则按需创建。
|
||||
/// </summary>
|
||||
public void SetCell(int row, int col, XTableCellData data)
|
||||
{
|
||||
XTableCoordinateUtility.CellToBlockCoordinate(
|
||||
row, col, BlockSizeX, BlockSizeY,
|
||||
out int blockRow, out int blockCol,
|
||||
out int localRow, out int localCol);
|
||||
|
||||
ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol);
|
||||
|
||||
if (!BlockMap.TryGetValue(zIndex, out XTableBlock block))
|
||||
{
|
||||
block = new XTableBlock(BlockSizeX, BlockSizeY, blockRow, blockCol);
|
||||
BlockMap[zIndex] = block;
|
||||
}
|
||||
|
||||
block.SetCell(localRow, localCol, data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 合并单元格
|
||||
|
||||
/// <summary>
|
||||
/// 合并从 (startRow, startCol) 开始、跨越 rowSpan 行和 colSpan 列的矩形区域。
|
||||
/// 合并源为左上角单元格 (startRow, startCol)。
|
||||
/// 如果合并区域跨越多个块,则每个涉及的块都会创建对应的 MergeDescriptor。
|
||||
/// </summary>
|
||||
public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan)
|
||||
{
|
||||
// 确保合并源单元格存在
|
||||
XTableCoordinateUtility.CellToBlockCoordinate(
|
||||
startRow, startCol, BlockSizeX, BlockSizeY,
|
||||
out int srcBlockRow, out int srcBlockCol,
|
||||
out int srcLocalRow, out int srcLocalCol);
|
||||
|
||||
ulong srcZIndex = XTableBlockMapping.BlockCoordToZIndex(srcBlockRow, srcBlockCol);
|
||||
|
||||
// 遍历合并区域内的所有单元格,分块处理
|
||||
for (int r = startRow; r < startRow + rowSpan; r++)
|
||||
{
|
||||
for (int c = startCol; c < startCol + colSpan; c++)
|
||||
{
|
||||
// 跳过合并源自身
|
||||
if (r == startRow && c == startCol) continue;
|
||||
|
||||
XTableCoordinateUtility.CellToBlockCoordinate(
|
||||
r, c, BlockSizeX, BlockSizeY,
|
||||
out int blockRow, out int blockCol,
|
||||
out int localRow, out int localCol);
|
||||
|
||||
ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol);
|
||||
|
||||
// 确保块存在
|
||||
if (!BlockMap.TryGetValue(zIndex, out XTableBlock block))
|
||||
{
|
||||
block = new XTableBlock(BlockSizeX, BlockSizeY, blockRow, blockCol);
|
||||
BlockMap[zIndex] = block;
|
||||
}
|
||||
|
||||
// 查找或创建此块的合并描述(相同源)
|
||||
XTableMergeDescriptor descriptor = FindOrCreateMergeDescriptor(block,
|
||||
zIndex == srcZIndex ? srcLocalRow : -1,
|
||||
zIndex == srcZIndex ? srcLocalCol : -1,
|
||||
srcZIndex, srcLocalRow, srcLocalCol,
|
||||
zIndex != srcZIndex);
|
||||
|
||||
descriptor.MergedCellSet.Add((localRow, localCol));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找或创建合并描述
|
||||
/// </summary>
|
||||
private XTableMergeDescriptor FindOrCreateMergeDescriptor(
|
||||
XTableBlock block, int checkLocalRow, int checkLocalCol,
|
||||
ulong srcZIndex, int srcLocalRow, int srcLocalCol, bool isCrossBlock)
|
||||
{
|
||||
// 查找已有描述
|
||||
for (int i = 0; i < block.MergeDescriptors.Count; i++)
|
||||
{
|
||||
var desc = block.MergeDescriptors[i];
|
||||
if (desc.MergeRowSpan > 0 && desc.MergeColSpan > 0)
|
||||
{
|
||||
// 简化的同源判断:跨块引用共享 sourceBlockIndex
|
||||
if (isCrossBlock && desc.IsCrossBlock && desc.SourceBlockIndex == srcZIndex)
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新描述
|
||||
var newDesc = new XTableMergeDescriptor
|
||||
{
|
||||
IsCrossBlock = isCrossBlock,
|
||||
SourceBlockIndex = srcZIndex,
|
||||
SourceLocalRow = srcLocalRow,
|
||||
SourceLocalCol = srcLocalCol
|
||||
};
|
||||
block.MergeDescriptors.Add(newDesc);
|
||||
return newDesc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消指定区域的合并
|
||||
/// </summary>
|
||||
public void UnmergeCells(int startRow, int startCol)
|
||||
{
|
||||
XTableCoordinateUtility.CellToBlockCoordinate(
|
||||
startRow, startCol, BlockSizeX, BlockSizeY,
|
||||
out int blockRow, out int blockCol,
|
||||
out int localRow, out int localCol);
|
||||
|
||||
ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol);
|
||||
|
||||
if (!BlockMap.TryGetValue(zIndex, out XTableBlock block))
|
||||
return;
|
||||
|
||||
// 移除包含此单元格的所有合并描述
|
||||
for (int i = block.MergeDescriptors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (block.MergeDescriptors[i].Contains(localRow, localCol))
|
||||
{
|
||||
block.MergeDescriptors.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 查询方法
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定块是否存在
|
||||
/// </summary>
|
||||
public bool HasBlock(int blockRow, int blockCol)
|
||||
{
|
||||
ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol);
|
||||
return BlockMap.ContainsKey(zIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取块数量
|
||||
/// </summary>
|
||||
public int BlockCount => BlockMap.Count;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e5d95e8d6d3d144d8fff6e41f175d77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 合并单元格描述——所有被合并单元格的 id 指向合并源(左上角单元格)。
|
||||
/// 同一类型支持本地块内引用和跨块引用,通过 <see cref="IsCrossBlock"/> 区分。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class XTableMergeDescriptor
|
||||
{
|
||||
/// <summary>合并区域在本地块内的起始行</summary>
|
||||
public int LocalStartRow;
|
||||
|
||||
/// <summary>合并区域在本地块内的起始列</summary>
|
||||
public int LocalStartCol;
|
||||
|
||||
/// <summary>合并跨越的行数</summary>
|
||||
public int MergeRowSpan;
|
||||
|
||||
/// <summary>合并跨越的列数</summary>
|
||||
public int MergeColSpan;
|
||||
|
||||
/// <summary>是否为跨块引用(true=合并源在另一个块上)</summary>
|
||||
public bool IsCrossBlock;
|
||||
|
||||
/// <summary>
|
||||
/// 跨块引用时:源块在字典中的 key(Z 曲线索引)
|
||||
/// 本地引用时:忽略此字段
|
||||
/// </summary>
|
||||
public ulong SourceBlockIndex;
|
||||
|
||||
/// <summary>源单元格在块内的行号(跨块时=源块的行、本地时=本块的行)</summary>
|
||||
public int SourceLocalRow;
|
||||
|
||||
/// <summary>源单元格在块内的列号(跨块时=源块的列、本地时=本块的列)</summary>
|
||||
public int SourceLocalCol;
|
||||
|
||||
/// <summary>本块内被合并的 (row, col) 集合,查询时快速判断是否需要重定向</summary>
|
||||
public HashSet<(int row, int col)> MergedCellSet;
|
||||
|
||||
public XTableMergeDescriptor()
|
||||
{
|
||||
MergedCellSet = new HashSet<(int, int)>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定本地坐标是否在此合并区域内
|
||||
/// </summary>
|
||||
public bool Contains(int localRow, int localCol)
|
||||
{
|
||||
return MergedCellSet.Contains((localRow, localCol));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取重定向目标——返回 (目标块Z索引, 目标块内行, 目标块内列)
|
||||
/// 本地引用时目标块索引为 0(由调用方忽略)
|
||||
/// </summary>
|
||||
public void GetRedirectTarget(out ulong targetBlockIndex, out int targetRow, out int targetCol)
|
||||
{
|
||||
targetBlockIndex = IsCrossBlock ? SourceBlockIndex : 0;
|
||||
targetRow = SourceLocalRow;
|
||||
targetCol = SourceLocalCol;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abe5a0c5395fa9249a66cd7195a49dbf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user