表格添加四叉树渲染优化逻辑。

This commit is contained in:
2026-06-30 19:53:12 +08:00
parent f7e489e924
commit 40fbfed45c
7 changed files with 1104 additions and 189 deletions
@@ -9,6 +9,7 @@ using XericUI.XTable.Core;
using XericUI.XTable.Mapping;
using XericUI.XTable.Rendering.Elements;
using XericLibrary.Runtime.SuperStyleSheet;
using XericUI.XTable.Core;
#if UNITY_EDITOR
using UnityEditor;
#endif
@@ -53,17 +54,16 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private CellAssembler m_Assembler;
[System.NonSerialized] private XTableDirtyFlag m_DirtyFlag;
private int m_SelectedRow = -1;
private int m_SelectedCol = -1;
// 多选范围
private int m_SelectStartRow = -1;
private int m_SelectStartCol = -1;
private int m_SelectEndRow = -1;
private int m_SelectEndCol = -1;
[System.NonSerialized] private XTableSelectionHandle m_SelectionHandle;
[System.NonSerialized] private GameObject m_BackgroundGo;
/// <summary>预制体实例缓存,key 为 (row, col),用于复用已实例化的预制体</summary>
[System.NonSerialized] private Dictionary<(int, int), GameObject> m_PrefabInstances;
/// <summary>表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限</summary>
[System.NonSerialized] private XTableQuadtree m_Quadtree;
private float m_TotalWidth;
private float m_TotalHeight;
@@ -100,14 +100,26 @@ namespace XericUI.XTable.Rendering.Component
public float[] ColWidths => m_ColWidths;
public string DefaultStyleNamespace => m_DefaultStyleNamespace;
public int SelectedRow => m_SelectedRow;
public int SelectedCol => m_SelectedCol;
public int SelectStartRow => m_SelectStartRow;
public int SelectStartCol => m_SelectStartCol;
public int SelectEndRow => m_SelectEndRow;
public int SelectEndCol => m_SelectEndCol;
public bool HasMultiSelection => m_SelectStartRow >= 0 && m_SelectEndRow >= 0;
/// <summary>选中操作句柄——外部可通过此句柄控制选区,或直接操作 StartRow/StartCol 等属性</summary>
public XTableSelectionHandle SelectionHandle
{
get
{
if (m_SelectionHandle == null)
m_SelectionHandle = new XTableSelectionHandle();
return m_SelectionHandle;
}
}
// ── 向后兼容属性(供 Editor 脚本使用)──
public int SelectedRow => SelectionHandle.StartRow;
public int SelectedCol => SelectionHandle.StartCol;
public int SelectStartRow => SelectionHandle.StartRow;
public int SelectStartCol => SelectionHandle.StartCol;
public int SelectEndRow => SelectionHandle.EndRow;
public int SelectEndCol => SelectionHandle.EndCol;
public bool HasMultiSelection => SelectionHandle.HasMultiSelection;
/// <summary>动态子对象在 Hierarchy 中是否可见(仅编辑器有效)</summary>
public bool ChildrenVisible
@@ -119,12 +131,214 @@ namespace XericUI.XTable.Rendering.Component
/// <summary>判断单元格是否在多选范围内</summary>
public bool IsInSelectionRange(int row, int col)
{
if (!HasMultiSelection) return false;
int minR = Mathf.Min(m_SelectStartRow, m_SelectEndRow);
int maxR = Mathf.Max(m_SelectStartRow, m_SelectEndRow);
int minC = Mathf.Min(m_SelectStartCol, m_SelectEndCol);
int maxC = Mathf.Max(m_SelectStartCol, m_SelectEndCol);
return row >= minR && row <= maxR && col >= minC && col <= maxC;
return SelectionHandle.IsInRange(row, col);
}
/// <summary>
/// 动态子对象挂载的父节点。
/// 若绑定了 ScrollRect 则使用其 Content,否则回退到自身的 rectTransform。
/// </summary>
public Transform ContentTransform
{
get
{
if (m_ScrollRect != null && m_ScrollRect.content != null)
return m_ScrollRect.content;
return rectTransform;
}
}
/// <summary>
/// 基于四叉树查询结果渲染可见象限。
/// 每个均匀象限渲染整块背景+边框,然后遍历象限内单元格渲染文本/预制体/选择高亮。
/// </summary>
private void RenderQuadrants(List<QuadrantResult> quadrants)
{
string ns = m_DefaultStyleNamespace;
#if UNITY_EDITOR
if (!Application.isPlaying)
{
Color borderColor = GetCellBorderColor(ns);
float borderWidth = GetCellBorderWidth(ns);
Color selectionColor = GetCellSelectionBgColor(ns);
Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * 0.4f);
Color focusColor = new Color(selectionColor.r + 0.04f, selectionColor.g + 0.13f, selectionColor.b + 0.13f, selectionColor.a * 1.4f);
foreach (var q in quadrants)
{
if (string.IsNullOrEmpty(q.Namespace)) continue;
Rect r = q.Region;
string qNs = q.Namespace;
// 象限整块背景
var bgImg = m_Assembler.GetImage("quad_bg", qNs);
bgImg.Rect.anchoredPosition = new Vector2(r.x, -r.y);
bgImg.Rect.sizeDelta = new Vector2(r.width, r.height);
bgImg.Image.color = GetCellBgColor(qNs);
// 象限底部边框
var hLine = m_Assembler.GetImage("quad_hline");
hLine.Rect.anchoredPosition = new Vector2(r.x, -r.y - r.height);
hLine.Rect.sizeDelta = new Vector2(r.width, borderWidth);
hLine.Image.color = borderColor;
// 象限右侧边框
var vLine = m_Assembler.GetImage("quad_vline");
vLine.Rect.anchoredPosition = new Vector2(r.x + r.width - borderWidth, -r.y);
vLine.Rect.sizeDelta = new Vector2(borderWidth, r.height);
vLine.Image.color = borderColor;
// 渲染象限内各单元格的文本/预制体/选择高亮
if (q.Cells != null)
{
foreach (var (row, col) in q.Cells)
{
// 跳过被合并覆盖的单元格
if (TableData.IsCellMerged(row, col)) continue;
float cellX = GetColLeftX(col);
float cellY = -GetRowTopY(row);
float cellW = m_ColWidths[col];
float cellH = m_RowHeights[row];
// 合并源:扩展尺寸
if (TableData.IsMergeSource(row, col, out int mergeRS, out int mergeCS))
{
for (int i = 1; i < mergeCS && col + i < m_ColWidths.Length; i++)
cellW += m_ColWidths[col + i];
for (int i = 1; i < mergeRS && row + i < m_RowHeights.Length; i++)
cellH += m_RowHeights[row + i];
}
XTableCellData data = TableData.GetCell(row, col);
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
? data.StyleNamespace : ns;
// 选择高亮
if (IsInSelectionRange(row, col))
{
var selImg = m_Assembler.GetImage("cell_multiselect");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
? focusColor : multiSelectColor;
}
else if (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
{
var selImg = m_Assembler.GetImage("cell_select");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = selectionColor;
}
if (data == null) continue;
if (!string.IsNullOrEmpty(data.Text))
{
var txt = m_Assembler.GetText("cell_text", cellNS);
txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY);
txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH);
txt.Text.text = data.Text;
txt.Text.color = GetCellFgColor(cellNS);
txt.Text.fontSize = GetCellFontSize(cellNS);
txt.Text.alignment = TextAlignmentOptions.Midline;
txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
}
if (data.Prefab != null)
PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
}
}
}
return;
}
#endif
// 运行时模式
Color rtSelectionColor = RT_SELECTION_COLOR;
Color rtMultiSelectColor = RT_MULTI_SELECT_COLOR;
Color rtFocusColor = RT_FOCUS_COLOR;
foreach (var q in quadrants)
{
if (string.IsNullOrEmpty(q.Namespace)) continue;
Rect r = q.Region;
string qNs = q.Namespace;
var bgImg = m_Assembler.GetImage("quad_bg", qNs);
bgImg.Rect.anchoredPosition = new Vector2(r.x, -r.y);
bgImg.Rect.sizeDelta = new Vector2(r.width, r.height);
bgImg.Image.color = GetCellBgColor(qNs);
var hLine = m_Assembler.GetImage("quad_hline");
hLine.Rect.anchoredPosition = new Vector2(r.x, -r.y - r.height);
hLine.Rect.sizeDelta = new Vector2(r.width, RT_BORDER_WIDTH);
hLine.Image.color = RT_BORDER_COLOR;
var vLine = m_Assembler.GetImage("quad_vline");
vLine.Rect.anchoredPosition = new Vector2(r.x + r.width - RT_BORDER_WIDTH, -r.y);
vLine.Rect.sizeDelta = new Vector2(RT_BORDER_WIDTH, r.height);
vLine.Image.color = RT_BORDER_COLOR;
if (q.Cells != null)
{
foreach (var (row, col) in q.Cells)
{
// 跳过被合并覆盖的单元格
if (TableData.IsCellMerged(row, col)) continue;
float cellX = GetColLeftX(col);
float cellY = -GetRowTopY(row);
float cellW = m_ColWidths[col];
float cellH = m_RowHeights[row];
// 合并源:扩展尺寸
if (TableData.IsMergeSource(row, col, out int mergeRS, out int mergeCS))
{
for (int i = 1; i < mergeCS && col + i < m_ColWidths.Length; i++)
cellW += m_ColWidths[col + i];
for (int i = 1; i < mergeRS && row + i < m_RowHeights.Length; i++)
cellH += m_RowHeights[row + i];
}
XTableCellData data = TableData.GetCell(row, col);
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
? data.StyleNamespace : ns;
if (IsInSelectionRange(row, col))
{
var selImg = m_Assembler.GetImage("cell_multiselect");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
? rtFocusColor : rtMultiSelectColor;
}
else if (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
{
var selImg = m_Assembler.GetImage("cell_select");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = rtSelectionColor;
}
if (data == null) continue;
if (!string.IsNullOrEmpty(data.Text))
{
var txt = m_Assembler.GetText("cell_text", cellNS);
txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY);
txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH);
txt.Text.text = data.Text;
}
if (data.Prefab != null)
PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
}
}
}
}
#endregion
@@ -141,7 +355,7 @@ namespace XericUI.XTable.Rendering.Component
if (m_TableData == null)
m_TableData = new XTableData();
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
RecalculateTotalSize();
EnsureDefaultStyleNamespace();
CreateDefaultSampleData();
@@ -191,6 +405,7 @@ namespace XericUI.XTable.Rendering.Component
// 销毁所有动态生成的对象
if (m_Assembler != null)
m_Assembler.DestroyAll();
ClearPrefabInstances();
if (m_BackgroundGo != null)
{
if (Application.isPlaying) Destroy(m_BackgroundGo);
@@ -422,13 +637,8 @@ namespace XericUI.XTable.Rendering.Component
newArr[i] = m_RowHeights[i + 1];
m_RowHeights = newArr;
// 更新选中状态
if (m_SelectedRow >= m_RowHeights.Length)
m_SelectedRow = Mathf.Max(-1, m_RowHeights.Length - 1);
if (m_SelectStartRow >= m_RowHeights.Length)
m_SelectStartRow = Mathf.Max(-1, m_RowHeights.Length - 1);
if (m_SelectEndRow >= m_RowHeights.Length)
m_SelectEndRow = Mathf.Max(-1, m_RowHeights.Length - 1);
// 裁剪选区到新尺寸
ClampSelectionToBounds();
MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
#if UNITY_EDITOR
@@ -474,13 +684,8 @@ namespace XericUI.XTable.Rendering.Component
newArr[i] = m_ColWidths[i + 1];
m_ColWidths = newArr;
// 更新选中状态
if (m_SelectedCol >= m_ColWidths.Length)
m_SelectedCol = Mathf.Max(-1, m_ColWidths.Length - 1);
if (m_SelectStartCol >= m_ColWidths.Length)
m_SelectStartCol = Mathf.Max(-1, m_ColWidths.Length - 1);
if (m_SelectEndCol >= m_ColWidths.Length)
m_SelectEndCol = Mathf.Max(-1, m_ColWidths.Length - 1);
// 裁剪选区到新尺寸
ClampSelectionToBounds();
MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
#if UNITY_EDITOR
@@ -488,53 +693,38 @@ namespace XericUI.XTable.Rendering.Component
#endif
}
/// <summary>设置外部选择句柄(替换内部句柄)</summary>
public void SetSelectionHandle(XTableSelectionHandle handle)
{
if (m_SelectionHandle != null)
m_SelectionHandle.OnSelectionChanged -= OnHandleSelectionChanged;
m_SelectionHandle = handle;
if (m_SelectionHandle != null)
m_SelectionHandle.OnSelectionChanged += OnHandleSelectionChanged;
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>单选一个单元格</summary>
public void SelectCell(int row, int col)
{
ClearMultiSelection();
m_SelectedRow = row;
m_SelectedCol = col;
SelectionHandle.Select(row, col);
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>多选一个范围</summary>
public void SelectCell(int startRow, int startCol, int endRow, int endCol)
{
SelectionHandle.Select(startRow, startCol, endRow, endCol);
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>清除选择</summary>
public void ClearSelection()
{
m_SelectedRow = -1;
m_SelectedCol = -1;
ClearMultiSelection();
SelectionHandle.Clear();
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>开始多选(以当前格为锚点)</summary>
public void StartMultiSelect(int row, int col)
{
m_SelectStartRow = row;
m_SelectStartCol = col;
m_SelectEndRow = row;
m_SelectEndCol = col;
m_SelectedRow = row;
m_SelectedCol = col;
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>扩展多选范围到目标格</summary>
public void ExtendMultiSelect(int row, int col)
{
m_SelectEndRow = row;
m_SelectEndCol = col;
m_SelectedRow = row;
m_SelectedCol = col;
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>清除多选范围</summary>
public void ClearMultiSelection()
{
m_SelectStartRow = -1;
m_SelectStartCol = -1;
m_SelectEndRow = -1;
m_SelectEndCol = -1;
}
/// <summary>通过文本选择一个单元格(如 "A0")</summary>
public bool SelectCellByText(string text)
{
@@ -551,30 +741,36 @@ namespace XericUI.XTable.Rendering.Component
out int sr, out int sc, out int er, out int ec))
return false;
ClearSelection();
m_SelectStartRow = sr;
m_SelectStartCol = sc;
m_SelectEndRow = er;
m_SelectEndCol = ec;
m_SelectedRow = sr;
m_SelectedCol = sc;
MarkDirty(TableDirtyType.SelectionChanged);
SelectCell(sr, sc, er, ec);
return true;
}
/// <summary>获取当前选区的文本描述(单格 "A0",多格 "A0:B1")</summary>
public string GetSelectionText()
{
if (m_SelectedRow < 0 || m_SelectedCol < 0) return string.Empty;
if (HasMultiSelection &&
(m_SelectStartRow != m_SelectEndRow || m_SelectStartCol != m_SelectEndCol))
if (!SelectionHandle.HasSelection) return string.Empty;
if (SelectionHandle.HasMultiSelection)
{
return XTableCoordinateFormat.RangeToText(
m_SelectStartRow, m_SelectStartCol,
m_SelectEndRow, m_SelectEndCol);
SelectionHandle.MinRow, SelectionHandle.MinCol,
SelectionHandle.MaxRow, SelectionHandle.MaxCol);
}
return XTableCoordinateFormat.CellToText(m_SelectedRow, m_SelectedCol);
return XTableCoordinateFormat.CellToText(
SelectionHandle.StartRow, SelectionHandle.StartCol);
}
private void OnHandleSelectionChanged(XTableSelectionHandle handle)
{
MarkDirty(TableDirtyType.SelectionChanged);
}
/// <summary>裁剪选区到当前行列尺寸范围内</summary>
private void ClampSelectionToBounds()
{
int maxR = (m_RowHeights?.Length ?? 0) - 1;
int maxC = (m_ColWidths?.Length ?? 0) - 1;
SelectionHandle.ClampTo(maxR, maxC);
}
public void RefreshView()
@@ -583,7 +779,7 @@ namespace XericUI.XTable.Rendering.Component
if (m_Assembler == null)
{
if (rectTransform != null)
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
else return;
}
@@ -607,14 +803,18 @@ namespace XericUI.XTable.Rendering.Component
bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
}
// 清理上一次渲染的预制体实例
ClearPrefabInstances();
// 回收池
m_Assembler.ReturnAll();
// 额外清理孤立的直接子对象(防止域重载或异常流程产生的残留)
Transform poolRoot = m_Assembler.PoolRoot;
for (int i = rectTransform.childCount - 1; i >= 0; i--)
Transform contentT = ContentTransform;
for (int i = contentT.childCount - 1; i >= 0; i--)
{
var child = rectTransform.GetChild(i);
var child = contentT.GetChild(i);
if ((poolRoot != null && child == poolRoot)) continue;
if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue;
@@ -625,14 +825,21 @@ namespace XericUI.XTable.Rendering.Component
DestroyImmediate(child.gameObject);
}
// 计算可见范围
int sr, er, sc, ec;
CalcVisibleRange(out sr, out er, out sc, out ec);
if (er <= sr || ec <= sc) return;
// 渲染每个可见单元格
RenderCells(sr, er, sc, ec);
// 渲染可见单元格:优先使用四叉树查询
if (m_Quadtree != null && m_Quadtree.IsBuilt)
{
var quadrants = new List<QuadrantResult>();
m_Quadtree.QueryVisible(m_VisibleRect, quadrants);
RenderQuadrants(quadrants);
}
else
{
// 回退:传统行列遍历
int sr, er, sc, ec;
CalcVisibleRange(out sr, out er, out sc, out ec);
if (er > sr && ec > sc)
RenderCells(sr, er, sc, ec);
}
m_DirtyFlag.Clear();
OnTableRefreshed?.Invoke();
@@ -667,13 +874,14 @@ namespace XericUI.XTable.Rendering.Component
/// <summary>销毁 transform 下所有子对象(用于域重载后清理残留)</summary>
private void ClearAllChildren()
{
if (rectTransform == null) return;
Transform t = ContentTransform;
if (t == null) return;
#if UNITY_EDITOR
// 编辑器模式下先重置 HideFlags 再销毁,防止 DontSave 对象拒绝销毁
while (rectTransform.childCount > 0)
while (t.childCount > 0)
{
var child = rectTransform.GetChild(0);
var child = t.GetChild(0);
child.gameObject.hideFlags = HideFlags.None;
if (Application.isPlaying)
Destroy(child.gameObject);
@@ -681,9 +889,9 @@ namespace XericUI.XTable.Rendering.Component
DestroyImmediate(child.gameObject);
}
#else
for (int i = rectTransform.childCount - 1; i >= 0; i--)
for (int i = t.childCount - 1; i >= 0; i--)
{
var child = rectTransform.GetChild(i);
var child = t.GetChild(i);
Destroy(child.gameObject);
}
#endif
@@ -694,10 +902,11 @@ namespace XericUI.XTable.Rendering.Component
{
m_ChildrenVisible = visible;
#if UNITY_EDITOR
if (rectTransform == null) return;
for (int i = rectTransform.childCount - 1; i >= 0; i--)
Transform t = ContentTransform;
if (t == null) return;
for (int i = t.childCount - 1; i >= 0; i--)
{
var child = rectTransform.GetChild(i);
var child = t.GetChild(i);
child.gameObject.hideFlags = visible
? HideFlags.DontSave
: HideFlags.DontSave | HideFlags.HideInHierarchy;
@@ -715,7 +924,7 @@ namespace XericUI.XTable.Rendering.Component
m_TableData = new XTableData();
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
RecalculateTotalSize();
CreateDefaultSampleData();
CreateBackground();
@@ -758,6 +967,48 @@ namespace XericUI.XTable.Rendering.Component
SetChildrenVisible(m_ChildrenVisible);
}
/// <summary>
/// 清空所有数据(单元格文本、合并信息),但保留行列排布和样式。
/// 等价于"清空内容但不改变表格结构"。
/// </summary>
public void ClearData()
{
if (m_TableData != null)
{
m_TableData.ClearAllMerges();
m_TableData.BlockMap.Clear();
}
ClearPrefabInstances();
m_Quadtree?.Clear();
ClearSelection();
MarkDirty(TableDirtyType.DataChanged);
RefreshView();
}
/// <summary>
/// 清空表格所有内容——数据、行列、样式,等同 <see cref="FullRebuild"/> 但不创建默认示例数据。
/// </summary>
public void ClearTable()
{
m_Assembler?.DestroyAll();
ClearPrefabInstances();
m_Quadtree?.Clear();
ClearAllChildren();
m_BackgroundGo = null;
m_TableData = new XTableData();
m_RowHeights = null;
m_ColWidths = null;
ClearSelection();
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
MarkDirty(TableDirtyType.All);
RecalculateTotalSize();
CreateBackground();
RefreshView();
SetChildrenVisible(m_ChildrenVisible);
}
/// <summary>仅强制刷新样式,不重建数据结构</summary>
public void ForceStyleRefresh()
{
@@ -848,10 +1099,10 @@ namespace XericUI.XTable.Rendering.Component
var selImg = m_Assembler.GetImage("cell_multiselect");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = (r == m_SelectedRow && c == m_SelectedCol)
selImg.Image.color = (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
? focusColor : multiSelectColor;
}
else if (r == m_SelectedRow && c == m_SelectedCol)
else if (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
{
var selImg = m_Assembler.GetImage("cell_select");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
@@ -884,6 +1135,10 @@ namespace XericUI.XTable.Rendering.Component
txt.Text.alignment = TextAlignmentOptions.Midline;
txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
}
// 预制体渲染
if (data.Prefab != null)
PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
}
}
return;
@@ -925,10 +1180,10 @@ namespace XericUI.XTable.Rendering.Component
var selImg = m_Assembler.GetImage("cell_multiselect");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
selImg.Image.color = (r == m_SelectedRow && c == m_SelectedCol)
selImg.Image.color = (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
? rtFocusColor : rtMultiSelectColor;
}
else if (r == m_SelectedRow && c == m_SelectedCol)
else if (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
{
var selImg = m_Assembler.GetImage("cell_select");
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
@@ -956,12 +1211,71 @@ namespace XericUI.XTable.Rendering.Component
txt.Text.text = data.Text;
// 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容
}
// 预制体渲染
if (data.Prefab != null)
PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
}
}
}
#endregion
#region 预制体管理
/// <summary>获取或创建指定单元格的预制体实例</summary>
private GameObject GetOrCreatePrefabInstance(int row, int col, GameObject prefab)
{
if (m_PrefabInstances == null)
m_PrefabInstances = new Dictionary<(int, int), GameObject>();
var key = (row, col);
if (m_PrefabInstances.TryGetValue(key, out GameObject existing) && existing != null)
return existing;
GameObject instance = Instantiate(prefab, ContentTransform);
instance.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
instance.name = $"_prefab_{row}_{col}";
m_PrefabInstances[key] = instance;
return instance;
}
/// <summary>定位预制体实例到指定单元格区域(锚点左上角,枢轴左上角)</summary>
private void PositionPrefabInstance(int row, int col, GameObject prefab, float cellX, float cellY, float cellW, float cellH)
{
GameObject instance = GetOrCreatePrefabInstance(row, col, prefab);
if (instance == null) return;
RectTransform prt = instance.GetComponent<RectTransform>();
if (prt == null) prt = instance.AddComponent<RectTransform>();
prt.anchorMin = new Vector2(0, 1);
prt.anchorMax = new Vector2(0, 1);
prt.pivot = new Vector2(0, 1);
prt.anchoredPosition = new Vector2(cellX, cellY);
prt.sizeDelta = new Vector2(cellW, cellH);
}
/// <summary>销毁所有已缓存的预制体实例</summary>
private void ClearPrefabInstances()
{
if (m_PrefabInstances == null) return;
foreach (var kv in m_PrefabInstances)
{
if (kv.Value != null)
{
if (Application.isPlaying)
Destroy(kv.Value);
else
DestroyImmediate(kv.Value);
}
}
m_PrefabInstances.Clear();
}
#endregion
#region 坐标计算
private float GetRowTopY(int row)
@@ -1116,7 +1430,14 @@ namespace XericUI.XTable.Rendering.Component
#region 其他
private void MarkDirty(TableDirtyType type) => m_DirtyFlag.Mark(type);
private void MarkDirty(TableDirtyType type)
{
m_DirtyFlag.Mark(type);
// 数据或布局变更时重建四叉树
if ((type & (TableDirtyType.DataChanged | TableDirtyType.LayoutChanged)) != 0)
MaintainQuadtree();
}
/// <summary>运行时样式变更回调:标记脏数据,下一帧重绘</summary>
private void OnPoolStyleChanged() => MarkDirty(TableDirtyType.StyleChanged);
@@ -1125,6 +1446,46 @@ namespace XericUI.XTable.Rendering.Component
#endregion
#region 四叉树
/// <summary>强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新)</summary>
public void ForceRebuildQuadtree()
{
if (m_Quadtree == null)
m_Quadtree = new XTableQuadtree();
MaintainQuadtree();
}
/// <summary>从当前 XTableData 收集单元格信息并重建四叉树</summary>
private void MaintainQuadtree()
{
if (m_TableData == null || m_RowHeights == null || m_ColWidths == null)
return;
if (m_Quadtree == null)
m_Quadtree = new XTableQuadtree();
// 收集所有有数据的单元格(跳过被合并覆盖的子单元格)
var cells = new List<XTableQuadtreeCell>();
for (int r = 0; r < m_RowHeights.Length; r++)
{
for (int c = 0; c < m_ColWidths.Length; c++)
{
if (m_TableData.IsCellMerged(r, c)) continue;
var data = m_TableData.GetCell(r, c);
if (data == null) continue;
string ns = !string.IsNullOrEmpty(data.StyleNamespace)
? data.StyleNamespace : m_DefaultStyleNamespace;
cells.Add(new XTableQuadtreeCell { Row = r, Col = c, Namespace = ns });
}
}
m_Quadtree.Build(m_TotalWidth, m_TotalHeight, m_RowHeights, m_ColWidths, cells);
}
#endregion
#region IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)