From 5c51d35d8e33879e925d958c37d8a9376d10cd7c Mon Sep 17 00:00:00 2001
From: LiRuoChen <571244399@qq.com>
Date: Wed, 29 Jul 2026 10:10:46 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC=E5=86=BB?=
=?UTF-8?q?=E7=BB=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Rendering/Component/TableRenderer.cs | 550 ++++++------------
.../Component/XericUIActionTable.Border.cs | 84 ++-
.../Component/XericUIActionTable.Data.cs | 15 +-
.../Component/XericUIActionTable.Freeze.cs | 237 +++++---
.../XericUIActionTable.RendererFilter.cs | 13 +-
.../Rendering/Component/XericUIActionTable.cs | 18 +-
.../Rendering/Elements/CellAssembler.cs | 14 +-
.../Rendering/Elements/PrefabPrototypePool.cs | 21 +-
.../Rendering/Elements/SlotCounterPool.cs | 52 +-
9 files changed, 453 insertions(+), 551 deletions(-)
diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs
index 63c5314..7a91345 100644
--- a/Runtime/XTable/Rendering/Component/TableRenderer.cs
+++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs
@@ -4,449 +4,253 @@ using UnityEngine;
using UnityEngine.UI;
using XericUI.XTable.Core;
using XericUI.XTable.Rendering.Elements;
-using XericLibrary.Runtime.SuperStyleSheet;
namespace XericUI.XTable.Rendering.Component
{
- ///
- /// 表格渲染管线封装——将 CellAssembler 对象池、背景创建、单元格渲染抽离为可复用单元。
- /// 同一表格可持有多个 TableRenderer 实例,分别渲染到不同父节点(如 Content 和 Viewport)。
- ///
- public class TableRenderer
+ /// 指定父级和索引范围的表格区域渲染器。池由表格全局持有,区域仅持有可见对象追踪。
+ public sealed class TableRenderer
{
- #region 内部字段
+ private struct Tracked
+ {
+ public CellAssembler.PooledText Text;
+ public CellAssembler.PooledImage Image;
+ public CellAssembler.PooledImage Selection;
+ }
- private Transform m_Parent;
- private CellAssembler m_Assembler;
- private GameObject m_BackgroundGo;
+ private readonly Transform m_Parent;
+ private readonly Dictionary<(int, int), Tracked> m_Tracked = new();
+ private readonly HashSet<(int, int)> m_Rendered = new();
+ private readonly Dictionary m_SpriteCache = new();
+ private GameObject m_BackgroundGo;
- /// 共享预制体对象池引用(由 XericUIActionTable 传入)
- public PrefabPrototypePool PrefabPool;
+ public CellAssembler Assembler;
+ public PrefabPrototypePool PrefabPool;
+ public Dictionary<(int, int), GameObject> ActivePrefabMap;
+ public float CellPadding;
+ public XTableConfig Config;
- /// 共享活跃预制体映射表(由 XericUIActionTable 传入)
- public Dictionary<(int, int), GameObject> ActivePrefabMap;
-
- /// Texture2D → Sprite 缓存,避免每帧分配
- private Dictionary m_SpriteCache;
-
- /// 单元格内容水平内边距(单侧),由 XericUIActionTable 设置
- public float CellPadding;
- private float CellPadding2 => CellPadding * 2f;
-
- /// 表格全局配置资源,由 XericUIActionTable 设置
- public XTableConfig Config;
-
- #endregion
-
- #region 公开属性
-
- public CellAssembler Assembler => m_Assembler;
- public GameObject BackgroundGo => m_BackgroundGo;
- public Transform Parent => m_Parent;
-
- /// 起始行偏移(Viewport 渲染器通常为 0;Content 渲染器通常为 freezeRowCount)
public int StartRow;
- /// 起始列偏移
public int StartCol;
- /// 终止行(不含),0 表示不受限
public int EndRow;
- /// 终止列(不含),0 表示不受限
public int EndCol;
- #endregion
-
- #region 样式委托
-
- /// 从样式表读取颜色值
public System.Func GetStyleColor;
- /// 从样式表读取浮点值
- public System.Func GetStyleFloat;
- /// 从样式表读取整数值
public System.Func GetStyleInt;
- /// 从样式表读取 TMP_FontAsset
public System.Func GetStyleFontAsset;
-
- /// 获取行高数组
public System.Func GetRowHeights;
- /// 获取列宽数组
public System.Func GetColWidths;
- /// 获取表格数据
public System.Func GetTableData;
- /// 默认样式命名空间
public System.Func GetDefaultStyleNamespace;
- /// 判断单元格是否在多选范围内
public System.Func IsInSelectionRange;
- /// 获取选中的起始行列
public System.Func GetSelectedRow;
- /// 获取选中的起始列
public System.Func GetSelectedCol;
- /// 运行时样式变更回调
- public event System.Action OnStyleChangeDetected;
+ public Transform Parent => m_Parent;
+ public GameObject BackgroundGo => m_BackgroundGo;
- #endregion
-
- #region 构造 / 销毁
-
- public TableRenderer(Transform parent, string defaultStyleNamespace = null,
- System.Action styleChangeHandler = null)
+ public TableRenderer(Transform parent)
{
m_Parent = parent;
- m_Assembler = new CellAssembler(parent, defaultStyleNamespace);
- if (styleChangeHandler != null)
- m_Assembler.OnStyleChangeDetected += styleChangeHandler;
}
- /// 彻底销毁渲染器中所有动态生成的对象
public void DestroyAll()
{
- m_Assembler?.DestroyAll();
-
- if (m_BackgroundGo != null)
- {
- if (Application.isPlaying)
- Object.Destroy(m_BackgroundGo);
- else
- Object.DestroyImmediate(m_BackgroundGo);
- m_BackgroundGo = null;
- }
+ ReleaseAll();
+ if (m_BackgroundGo == null) return;
+ if (Application.isPlaying) Object.Destroy(m_BackgroundGo);
+ else Object.DestroyImmediate(m_BackgroundGo);
+ m_BackgroundGo = null;
}
- #endregion
-
- #region 背景
-
- /// 创建或获取背景 Image(锚定左上角,覆盖整个渲染范围)
- public void CreateBackground(float totalWidth, float totalHeight, string styleNamespace)
+ public void CreateBackground(float width, float height, string styleNamespace, Vector2 position = default)
{
- if (m_BackgroundGo != null)
+ if (m_BackgroundGo == null)
{
- var rt = m_BackgroundGo.GetComponent();
- rt.sizeDelta = new Vector2(totalWidth, totalHeight);
+ m_BackgroundGo = new GameObject("_TableBackground", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
+ m_BackgroundGo.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
+ m_BackgroundGo.transform.SetParent(m_Parent, false);
+ m_BackgroundGo.transform.SetAsFirstSibling();
+ var image = m_BackgroundGo.GetComponent();
+ image.raycastTarget = false;
+ image.color = GetStyleColor?.Invoke(styleNamespace, XericUIActionTable.STYLE_CELL_BG_COLOR,
+ XericUIActionTable.BG_COLOR_FALLBACK) ?? XericUIActionTable.BG_COLOR_FALLBACK;
+ }
+ var rt = m_BackgroundGo.GetComponent();
+ rt.anchorMin = new Vector2(0, 1);
+ rt.anchorMax = new Vector2(0, 1);
+ rt.pivot = new Vector2(0, 1);
+ rt.anchoredPosition = position;
+ rt.sizeDelta = new Vector2(width, height);
+ }
+
+ public void Render(int sr, int er, int sc, int ec, bool editorMode, bool fullRefresh)
+ {
+ var rows = GetRowHeights?.Invoke();
+ var cols = GetColWidths?.Invoke();
+ if (Assembler == null || rows == null || cols == null) return;
+ sr = Mathf.Max(sr, StartRow);
+ sc = Mathf.Max(sc, StartCol);
+ er = Mathf.Min(er, EndRow > 0 ? EndRow : rows.Length);
+ ec = Mathf.Min(ec, EndCol > 0 ? EndCol : cols.Length);
+ if (sr >= er || sc >= ec)
+ {
+ if (fullRefresh) ReleaseAll();
return;
}
- m_BackgroundGo = new GameObject("_TableBackground",
- typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
- m_BackgroundGo.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
- m_BackgroundGo.transform.SetParent(m_Parent, false);
- m_BackgroundGo.transform.SetAsFirstSibling();
+ var target = new HashSet<(int, int)>();
+ for (int r = sr; r < er; r++)
+ for (int c = sc; c < ec; c++)
+ target.Add((r, c));
- RectTransform bgRt = m_BackgroundGo.GetComponent();
- bgRt.anchorMin = new Vector2(0, 1);
- bgRt.anchorMax = new Vector2(0, 1);
- bgRt.pivot = new Vector2(0, 1);
- bgRt.anchoredPosition = Vector2.zero;
- bgRt.sizeDelta = new Vector2(totalWidth, totalHeight);
-
- var bgImg = m_BackgroundGo.GetComponent();
- bgImg.color = GetStyleColor?.Invoke(styleNamespace,
- XericUIActionTable.STYLE_CELL_BG_COLOR,
- XericUIActionTable.BG_COLOR_FALLBACK)
- ?? XericUIActionTable.BG_COLOR_FALLBACK;
- bgImg.raycastTarget = false;
- }
-
- #endregion
-
- #region 单元格渲染
-
- /// 回收所有池对象
- public void ReturnAll() => m_Assembler.ReturnAll();
-
- ///
- /// 渲染指定范围内的单元格。
- /// 行范围为 [sr, er),列范围为 [sc, ec)。
- ///
- public void RenderCells(int sr, int er, int sc, int ec, bool isEditor)
- {
- float[] rowHeights = GetRowHeights?.Invoke();
- float[] colWidths = GetColWidths?.Invoke();
- XTableData tableData = GetTableData?.Invoke();
- string defaultNs = GetDefaultStyleNamespace?.Invoke() ?? "xeric_table_default";
-
- if (rowHeights == null || colWidths == null) return;
- if (rowHeights.Length == 0 || colWidths.Length == 0) return;
-
- // 应用渲染范围限制
- int renderStartRow = StartRow;
- int renderEndRow = EndRow > 0 ? Mathf.Min(EndRow, rowHeights.Length) : rowHeights.Length;
- int renderStartCol = StartCol;
- int renderEndCol = EndCol > 0 ? Mathf.Min(EndCol, colWidths.Length) : colWidths.Length;
-
- sr = Mathf.Max(sr, renderStartRow);
- er = Mathf.Min(er, renderEndRow);
- sc = Mathf.Max(sc, renderStartCol);
- ec = Mathf.Min(ec, renderEndCol);
-
- if (sr >= er || sc >= ec) return;
-
- if (isEditor)
- {
- RenderCellsEditor(sr, er, sc, ec, rowHeights, colWidths, tableData, defaultNs);
- }
+ if (fullRefresh)
+ ReleaseAll();
else
+ ReleaseExcept(target);
+
+ foreach (var cell in target)
+ if (fullRefresh || !m_Rendered.Contains(cell))
+ RenderCell(cell.Item1, cell.Item2, rows, cols, editorMode);
+
+ m_Rendered.Clear();
+ foreach (var cell in target) m_Rendered.Add(cell);
+ }
+
+ public void ReleaseAll()
+ {
+ foreach (var pair in m_Tracked)
+ ReleaseTracked(pair.Value);
+ m_Tracked.Clear();
+ m_Rendered.Clear();
+ if (ActivePrefabMap == null) return;
+ var keys = new List<(int, int)>();
+ foreach (var pair in ActivePrefabMap) keys.Add(pair.Key);
+ foreach (var key in keys) ActivePrefabMap.Remove(key);
+ }
+
+ private void ReleaseExcept(HashSet<(int, int)> target)
+ {
+ var remove = new List<(int, int)>();
+ foreach (var pair in m_Tracked)
+ if (!target.Contains(pair.Key)) remove.Add(pair.Key);
+ foreach (var key in remove)
{
- RenderCellsRuntime(sr, er, sc, ec, rowHeights, colWidths, tableData, defaultNs);
+ ReleaseTracked(m_Tracked[key]);
+ m_Tracked.Remove(key);
}
}
- private void RenderCellsEditor(int sr, int er, int sc, int ec,
- float[] rowHeights, float[] colWidths, XTableData tableData, string defaultNs)
+ private void ReleaseTracked(Tracked tracked)
{
- Color selectionColor = GetStyleColor(defaultNs, XericUIActionTable.STYLE_CELL_SELECTION_BG_COLOR,
- XericUIActionTable.SELECTION_COLOR_FALLBACK);
+ Assembler.ReleaseText(tracked.Text);
+ Assembler.ReleaseImage(tracked.Image);
+ Assembler.ReleaseImage(tracked.Selection);
+ }
- Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor);
- Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul);
-
- int selRow = GetSelectedRow();
- int selCol = GetSelectedCol();
-
- for (int r = sr; r < er && r < rowHeights.Length; r++)
+ private void RenderCell(int row, int col, float[] rows, float[] cols, bool editorMode)
+ {
+ var data = GetTableData?.Invoke();
+ if (data != null && data.IsCellMerged(row, col)) return;
+ string defaultNs = GetDefaultStyleNamespace?.Invoke() ?? "xeric_table_default";
+ string ns = data?.GetCell(row, col)?.GetEffectiveStyleNamespace(defaultNs) ?? defaultNs;
+ float x = CalcColLeftX(col, cols);
+ float y = -CalcRowTopY(row, rows);
+ float width = cols[col];
+ float height = rows[row];
+ if (data != null && data.IsMergeSource(row, col, out int mergeRows, out int mergeCols))
{
- for (int c = sc; c < ec && c < colWidths.Length; c++)
+ for (int i = 1; i < mergeCols && col + i < cols.Length; i++) width += cols[col + i];
+ for (int i = 1; i < mergeRows && row + i < rows.Length; i++) height += rows[row + i];
+ }
+
+ int cellId = row * cols.Length + col;
+ var tracked = new Tracked();
+ bool selected = IsInSelectionRange?.Invoke(row, col) == true ||
+ (row == GetSelectedRow?.Invoke() && col == GetSelectedCol?.Invoke());
+ if (selected)
+ {
+ var selection = Assembler.GetImage(cellId, "cell_selection", ns, m_Parent);
+ if (selection != null)
{
- if (tableData != null && tableData.IsCellMerged(r, c)) continue;
+ selection.Rect.anchoredPosition = new Vector2(x, y);
+ selection.Rect.sizeDelta = new Vector2(width, height);
+ selection.Image.color = editorMode
+ ? GetStyleColor(ns, XericUIActionTable.STYLE_CELL_SELECTION_BG_COLOR, XericUIActionTable.SELECTION_COLOR_FALLBACK)
+ : Config.RuntimeSelectionColor;
+ tracked.Selection = selection;
+ }
+ }
- float cellX = CalcColLeftX(c, colWidths);
- float cellY = -CalcRowTopY(r, rowHeights);
- float cellW = colWidths[c];
- float cellH = rowHeights[r];
-
- if (tableData != null && tableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
+ var cell = data?.GetCell(row, col);
+ if (cell != null)
+ {
+ if (cell.ContentType == CellContentType.Text && !string.IsNullOrEmpty(cell.Text))
+ {
+ var text = Assembler.GetText(cellId, "cell_text", ns, m_Parent);
+ if (text != null)
{
- for (int i = 1; i < mergeCS && c + i < colWidths.Length; i++)
- cellW += colWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < rowHeights.Length; i++)
- cellH += rowHeights[r + i];
- }
-
- XTableCellData data = tableData?.GetCell(r, c);
- string cellNS = data != null ? data.GetEffectiveStyleNamespace(defaultNs) : defaultNs;
- int cellId = r * colWidths.Length + c;
-
- // 选择高亮
- if (IsInSelectionRange != null && IsInSelectionRange(r, c))
- {
- var selImg = m_Assembler.GetImage(cellId, "cell_multiselect");
- if (selImg != null)
+ text.Rect.anchoredPosition = new Vector2(x + CellPadding, y);
+ text.Rect.sizeDelta = new Vector2(width - CellPadding * 2f, height);
+ text.Text.SetText(cell.Text);
+ if (editorMode)
{
- selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
- selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
- selImg.Image.color = (r == selRow && c == selCol) ? focusColor : multiSelectColor;
+ text.Text.color = GetStyleColor(ns, XericUIActionTable.STYLE_CELL_FG_COLOR, XericUIActionTable.FG_COLOR_FALLBACK);
+ text.Text.fontSize = GetStyleInt(ns, XericUIActionTable.STYLE_CELL_FONT_SIZE, XericUIActionTable.FONT_SIZE_FALLBACK);
+ text.Text.font = GetStyleFontAsset?.Invoke(ns, XericUIActionTable.STYLE_CELL_FONT_ASSET) ?? text.Text.font;
}
+ tracked.Text = text;
}
- else if (r == selRow && c == selCol)
+ }
+ else if (cell.ContentType == CellContentType.Image && cell.Image != null)
+ {
+ var image = Assembler.GetImage(cellId, "cell_image", ns, m_Parent);
+ if (image != null)
{
- var selImg = m_Assembler.GetImage(cellId, "cell_select");
- if (selImg != null)
- {
- selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
- selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
- selImg.Image.color = selectionColor;
- }
+ image.Rect.anchoredPosition = new Vector2(x + CellPadding, y);
+ image.Rect.sizeDelta = new Vector2(width - CellPadding * 2f, height);
+ image.Image.sprite = GetSprite(cell.Image);
+ tracked.Image = image;
}
-
- if (data == null) continue;
-
- switch (data.ContentType)
+ }
+ else if (cell.ContentType == CellContentType.Prefab && cell.Prefab != null && PrefabPool != null)
+ {
+ var prefab = PrefabPool.Acquire(cell.Prefab, cellId, m_Parent);
+ if (prefab != null)
{
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText(cellId, "cell_text", cellNS);
- if (txt != null)
- {
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.SetText(data.Text);
- txt.Text.color = GetStyleColor(cellNS, XericUIActionTable.STYLE_CELL_FG_COLOR,
- XericUIActionTable.FG_COLOR_FALLBACK);
- txt.Text.fontSize = GetStyleInt(cellNS, XericUIActionTable.STYLE_CELL_FONT_SIZE,
- XericUIActionTable.FONT_SIZE_FALLBACK);
- txt.Text.alignment = TextAlignmentOptions.Midline;
- txt.Text.font = GetStyleFontAsset?.Invoke(cellNS,
- XericUIActionTable.STYLE_CELL_FONT_ASSET) ?? txt.Text.font;
- }
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage(cellId, "cell_image", cellNS);
- if (img != null)
- {
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null && PrefabPool != null)
- {
- GameObject instance = PrefabPool.Acquire(data.Prefab, cellId);
- if (instance != null)
- {
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- if (ActivePrefabMap != null)
- ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- }
- break;
+ var rt = prefab.GetComponent();
+ rt.anchoredPosition = new Vector2(x, y);
+ rt.sizeDelta = new Vector2(width, height);
+ if (ActivePrefabMap != null)
+ ActivePrefabMap[(row, col)] = prefab;
+ cell.OnPrefabAcquire?.Invoke(prefab);
}
}
}
+ m_Tracked[(row, col)] = tracked;
}
- private void RenderCellsRuntime(int sr, int er, int sc, int ec,
- float[] rowHeights, float[] colWidths, XTableData tableData, string defaultNs)
+ private Sprite GetSprite(Texture2D texture)
{
- Color rtSelectionColor = Config.RuntimeSelectionColor;
- Color rtMultiSelectColor = Config.RuntimeMultiSelectColor;
- Color rtFocusColor = Config.RuntimeFocusColor;
-
- int selRow = GetSelectedRow();
- int selCol = GetSelectedCol();
-
- for (int r = sr; r < er && r < rowHeights.Length; r++)
+ if (!m_SpriteCache.TryGetValue(texture, out var sprite))
{
- for (int c = sc; c < ec && c < colWidths.Length; c++)
- {
- if (tableData != null && tableData.IsCellMerged(r, c)) continue;
-
- float cellX = CalcColLeftX(c, colWidths);
- float cellY = -CalcRowTopY(r, rowHeights);
- float cellW = colWidths[c];
- float cellH = rowHeights[r];
-
- if (tableData != null && tableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
- {
- for (int i = 1; i < mergeCS && c + i < colWidths.Length; i++)
- cellW += colWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < rowHeights.Length; i++)
- cellH += rowHeights[r + i];
- }
-
- XTableCellData data = tableData?.GetCell(r, c);
- string cellNS = data != null ? data.GetEffectiveStyleNamespace(defaultNs) : defaultNs;
- int cellId = r * colWidths.Length + c;
-
- if (IsInSelectionRange != null && IsInSelectionRange(r, c))
- {
- var selImg = m_Assembler.GetImage(cellId, "cell_multiselect");
- if (selImg != null)
- {
- selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
- selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
- selImg.Image.color = (r == selRow && c == selCol) ? rtFocusColor : rtMultiSelectColor;
- }
- }
- else if (r == selRow && c == selCol)
- {
- var selImg = m_Assembler.GetImage(cellId, "cell_select");
- if (selImg != null)
- {
- selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
- selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
- selImg.Image.color = rtSelectionColor;
- }
- }
-
- if (data == null) continue;
-
- switch (data.ContentType)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText(cellId, "cell_text", cellNS);
- if (txt != null)
- {
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.SetText(data.Text);
- }
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage(cellId, "cell_image", cellNS);
- if (img != null)
- {
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null && PrefabPool != null)
- {
- GameObject instance = PrefabPool.Acquire(data.Prefab, cellId);
- if (instance != null)
- {
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- if (ActivePrefabMap != null)
- ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- }
- break;
- }
- }
- }
- }
-
- #endregion
-
- #region 辅助方法
-
- /// 将 Texture2D 转为 Sprite(带缓存),用于 Image 类型单元格渲染
- private Sprite GetOrCreateSprite(Texture2D texture)
- {
- if (texture == null) return null;
-
- if (m_SpriteCache == null)
- m_SpriteCache = new Dictionary();
-
- if (!m_SpriteCache.TryGetValue(texture, out Sprite sprite))
- {
- sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
- m_SpriteCache[texture] = sprite;
+ sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(.5f, .5f));
+ m_SpriteCache.Add(texture, sprite);
}
return sprite;
}
- #endregion
-
- #region 坐标计算
-
- private float CalcRowTopY(int row, float[] rowHeights)
+ private static float CalcRowTopY(int row, float[] values)
{
- float y = 0;
- for (int i = 0; i < row && i < rowHeights.Length; i++)
- y += rowHeights[i];
- return y;
+ float result = 0;
+ for (int i = 0; i < row; i++) result += values[i];
+ return result;
}
- private float CalcColLeftX(int col, float[] colWidths)
+ private static float CalcColLeftX(int col, float[] values)
{
- float x = 0;
- for (int i = 0; i < col && i < colWidths.Length; i++)
- x += colWidths[i];
- return x;
+ float result = 0;
+ for (int i = 0; i < col; i++) result += values[i];
+ return result;
}
-
- #endregion
}
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs
index cc5a0d4..fdbca16 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs
@@ -77,16 +77,27 @@ namespace XericUI.XTable.Rendering.Component
int rows = m_RowHeights.Length;
int cols = m_ColWidths.Length;
- for (int boundary = 0; boundary <= rows; boundary++)
- SubmitHorizontalBoundary(renderer, boundary, cols);
- for (int boundary = 0; boundary <= cols; boundary++)
- SubmitVerticalBoundary(renderer, boundary, rows);
+ int freezeRows = Mathf.Clamp(m_FreezeRowCount, 0, rows);
+ int freezeCols = Mathf.Clamp(m_FreezeColCount, 0, cols);
+ SubmitBorders(renderer, freezeRows, rows, freezeCols, cols);
+ RebuildFrozenBorders(freezeRows, freezeCols, rows, cols);
}
- private void SubmitHorizontalBoundary(UILineRendererV2 renderer, int boundary, int cols)
+ private void SubmitBorders(UILineRendererV2 renderer, int rowStart, int rowEnd, int colStart, int colEnd)
{
- int start = 0;
- while (start < cols)
+ if (renderer == null || rowStart >= rowEnd || colStart >= colEnd) return;
+ renderer.ClearAll();
+ renderer.rectTransform.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
+ for (int boundary = rowStart; boundary <= rowEnd; boundary++)
+ SubmitHorizontalBoundary(renderer, boundary, colStart, colEnd);
+ for (int boundary = colStart; boundary <= colEnd; boundary++)
+ SubmitVerticalBoundary(renderer, boundary, rowStart, rowEnd);
+ }
+
+ private void SubmitHorizontalBoundary(UILineRendererV2 renderer, int boundary, int colStart, int colEnd)
+ {
+ int start = colStart;
+ while (start < colEnd)
{
if (IsMergedInteriorBoundary(XTableBorderAxis.Horizontal, boundary, start))
{
@@ -95,7 +106,7 @@ namespace XericUI.XTable.Rendering.Component
}
string ns = ResolveBorderNamespace(XTableBorderAxis.Horizontal, boundary, start);
int end = start + 1;
- while (end < cols && !IsMergedInteriorBoundary(XTableBorderAxis.Horizontal, boundary, end)
+ while (end < colEnd && !IsMergedInteriorBoundary(XTableBorderAxis.Horizontal, boundary, end)
&& ResolveBorderNamespace(XTableBorderAxis.Horizontal, boundary, end) == ns)
end++;
@@ -106,10 +117,10 @@ namespace XericUI.XTable.Rendering.Component
}
}
- private void SubmitVerticalBoundary(UILineRendererV2 renderer, int boundary, int rows)
+ private void SubmitVerticalBoundary(UILineRendererV2 renderer, int boundary, int rowStart, int rowEnd)
{
- int start = 0;
- while (start < rows)
+ int start = rowStart;
+ while (start < rowEnd)
{
if (IsMergedInteriorBoundary(XTableBorderAxis.Vertical, boundary, start))
{
@@ -118,7 +129,7 @@ namespace XericUI.XTable.Rendering.Component
}
string ns = ResolveBorderNamespace(XTableBorderAxis.Vertical, boundary, start);
int end = start + 1;
- while (end < rows && !IsMergedInteriorBoundary(XTableBorderAxis.Vertical, boundary, end)
+ while (end < rowEnd && !IsMergedInteriorBoundary(XTableBorderAxis.Vertical, boundary, end)
&& ResolveBorderNamespace(XTableBorderAxis.Vertical, boundary, end) == ns)
end++;
@@ -129,6 +140,55 @@ namespace XericUI.XTable.Rendering.Component
}
}
+ private void RebuildFrozenBorders(int freezeRows, int freezeCols, int rows, int cols)
+ {
+ if (freezeRows == 0 && freezeCols == 0)
+ {
+ DestroyFrozenBorderRenderers();
+ return;
+ }
+ if (freezeCols > 0 && freezeRows < rows)
+ SubmitBorders(GetOrCreateFrozenBorderRenderer(m_FrozenColumnsContainer, ref m_FrozenColumnBorderRenderer, "_FrozenColumnBorders"),
+ freezeRows, rows, 0, freezeCols);
+ if (freezeRows > 0 && freezeCols < cols)
+ SubmitBorders(GetOrCreateFrozenBorderRenderer(m_FrozenRowsContainer, ref m_FrozenRowBorderRenderer, "_FrozenRowBorders"),
+ 0, freezeRows, freezeCols, cols);
+ if (freezeRows > 0 && freezeCols > 0)
+ SubmitBorders(GetOrCreateFrozenBorderRenderer(m_FrozenCornerContainer, ref m_FrozenCornerBorderRenderer, "_FrozenCornerBorders"),
+ 0, freezeRows, 0, freezeCols);
+ }
+
+ private static UILineRendererV2 GetOrCreateFrozenBorderRenderer(Transform parent,
+ ref UILineRendererV2 renderer, string name)
+ {
+ if (renderer != null) return renderer;
+ if (parent == null) return null;
+ var go = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(UILineRendererV2));
+ go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
+ go.transform.SetParent(parent, false);
+ var rt = go.GetComponent();
+ rt.anchorMin = new Vector2(0, 1);
+ rt.anchorMax = new Vector2(0, 1);
+ rt.pivot = new Vector2(0, 1);
+ renderer = go.GetComponent();
+ return renderer;
+ }
+
+ private void DestroyFrozenBorderRenderers()
+ {
+ DestroyFrozenBorderRenderer(ref m_FrozenColumnBorderRenderer);
+ DestroyFrozenBorderRenderer(ref m_FrozenRowBorderRenderer);
+ DestroyFrozenBorderRenderer(ref m_FrozenCornerBorderRenderer);
+ }
+
+ private static void DestroyFrozenBorderRenderer(ref UILineRendererV2 renderer)
+ {
+ if (renderer == null) return;
+ if (Application.isPlaying) Destroy(renderer.gameObject);
+ else DestroyImmediate(renderer.gameObject);
+ renderer = null;
+ }
+
private string ResolveBorderNamespace(XTableBorderAxis axis, int fixedIndex, int index)
{
string ns = m_TableData?.GetBorderNamespace(axis, fixedIndex, index);
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs
index 2928a45..28b13af 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs
@@ -28,6 +28,16 @@ namespace XericUI.XTable.Rendering.Component
#endregion
+ #region 渲染分区
+
+ private bool IsNormalRegionCell(int row, int col)
+ {
+ return row >= Mathf.Clamp(m_FreezeRowCount, 0, m_RowHeights?.Length ?? 0)
+ && col >= Mathf.Clamp(m_FreezeColCount, 0, m_ColWidths?.Length ?? 0);
+ }
+
+ #endregion
+
#region 坐标公式
private float GetRowTopY(int row)
@@ -104,10 +114,7 @@ namespace XericUI.XTable.Rendering.Component
// Content 的 anchor/pivot 在 Awake 中强制设为 (0,1),anchoredPosition
// 直接表示 Content 左上角相对于 Viewport 左上角的偏移。
Vector2 ap = ct.anchoredPosition;
- float visibleLeft = ap.x;
- float visibleTop = ap.y;
-
- m_VisibleRect = new Rect(visibleLeft, visibleTop, vt.rect.width, vt.rect.height);
+ m_VisibleRect = new Rect(ap.x, ap.y, vt.rect.width, vt.rect.height);
}
private void CalcVisibleRange(out int sr, out int er, out int sc, out int ec)
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs
index dbfe628..df362ec 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs
@@ -1,117 +1,164 @@
-using System;
using UnityEngine;
-using UnityEngine.UI;
-using XericUI.XTable.Core;
using XericUI.XTable.Rendering.Elements;
-using XericLibrary.Runtime.SuperStyleSheet;
namespace XericUI.XTable.Rendering.Component
{
public partial class XericUIActionTable
{
- #region Viewport 冻结渲染
+ private static readonly Vector2 FrozenAnchor = new(0f, 1f);
- /// 创建或重新创建 Viewport 层冻结渲染器
private void CreateViewportRenderer()
{
if (m_ScrollRect == null || m_ScrollRect.viewport == null) return;
- if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0) return;
-
- // 清理旧渲染器
- m_ViewportRenderer?.DestroyAll();
-
- Transform viewportT = m_ScrollRect.viewport;
- m_ViewportRenderer = new TableRenderer(viewportT, m_DefaultStyleNamespace, OnPoolStyleChanged);
-
- // 配置委托
- m_ViewportRenderer.GetStyleColor = (ns, path, fb) => GetStyleColor(ns, path, fb);
- m_ViewportRenderer.GetStyleFloat = (ns, path, fb) => GetStyleFloat(ns, path, fb);
- m_ViewportRenderer.GetStyleInt = (ns, path, fb) => GetStyleInt(ns, path, fb);
- m_ViewportRenderer.GetStyleFontAsset = (ns, path) => GetStyleFontAsset(ns, path);
- m_ViewportRenderer.GetRowHeights = () => m_RowHeights;
- m_ViewportRenderer.GetColWidths = () => m_ColWidths;
- m_ViewportRenderer.GetTableData = () => m_TableData;
- m_ViewportRenderer.GetDefaultStyleNamespace = () => m_DefaultStyleNamespace;
- m_ViewportRenderer.IsInSelectionRange = (r, c) => IsInSelectionRange(r, c);
- m_ViewportRenderer.GetSelectedRow = () => SelectionHandle.StartRow;
- m_ViewportRenderer.GetSelectedCol = () => SelectionHandle.StartCol;
-
- // 仅渲染冻结范围
- m_ViewportRenderer.StartRow = 0;
- m_ViewportRenderer.EndRow = m_FreezeRowCount;
- m_ViewportRenderer.StartCol = 0;
- m_ViewportRenderer.EndCol = m_FreezeColCount;
-
- // 共享预制体对象池
- m_ViewportRenderer.PrefabPool = m_PrefabPool;
- m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap;
- m_ViewportRenderer.CellPadding = Config.CellHorizontalPadding;
- m_ViewportRenderer.Config = Config;
+ CreateFrozenContainers();
+ CreateFrozenRenderers();
}
- /// 渲染 Viewport 层冻结单元格,随滚动偏移调整位置
- private void RenderFrozenCells()
+ private void CreateFrozenContainers()
{
- if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0)
+ Transform viewport = m_ScrollRect.viewport;
+ m_FrozenColumnsContainer = CreateFrozenContainer(viewport, "_FrozenColumns");
+ m_FrozenRowsContainer = CreateFrozenContainer(viewport, "_FrozenRows");
+ m_FrozenCornerContainer = CreateFrozenContainer(viewport, "_FrozenCorner");
+ }
+
+ private static RectTransform CreateFrozenContainer(Transform parent, string name)
+ {
+ var existing = parent.Find(name) as RectTransform;
+ if (existing != null) return existing;
+ var go = new GameObject(name, typeof(RectTransform));
+ go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
+ go.transform.SetParent(parent, false);
+ var rt = go.GetComponent();
+ rt.anchorMin = FrozenAnchor;
+ rt.anchorMax = FrozenAnchor;
+ rt.pivot = FrozenAnchor;
+ return rt;
+ }
+
+ private void CreateFrozenRenderers()
+ {
+ if (m_FrozenColumnsContainer == null) return;
+ m_FrozenColumnRenderer?.DestroyAll();
+ m_FrozenRowRenderer?.DestroyAll();
+ m_FrozenCornerRenderer?.DestroyAll();
+ DestroyFrozenBorderRenderers();
+ m_FrozenColumnRenderer = CreateFrozenRenderer(m_FrozenColumnsContainer);
+ m_FrozenRowRenderer = CreateFrozenRenderer(m_FrozenRowsContainer);
+ m_FrozenCornerRenderer = CreateFrozenRenderer(m_FrozenCornerContainer);
+ }
+
+ private TableRenderer CreateFrozenRenderer(Transform parent)
+ {
+ var renderer = new TableRenderer(parent)
{
- // 无冻结需求时清理渲染器
- if (m_ViewportRenderer != null)
- {
- m_ViewportRenderer.DestroyAll();
- m_ViewportRenderer = null;
- }
+ Assembler = m_Assembler,
+ PrefabPool = m_PrefabPool,
+ CellPadding = CellPadding,
+ Config = Config,
+ GetStyleColor = (ns, path, fallback) => GetStyleColor(ns, path, fallback),
+ GetStyleInt = (ns, path, fallback) => GetStyleInt(ns, path, fallback),
+ GetStyleFontAsset = (ns, path) => GetStyleFontAsset(ns, path),
+ GetRowHeights = () => m_RowHeights,
+ GetColWidths = () => m_ColWidths,
+ GetTableData = () => m_TableData,
+ GetDefaultStyleNamespace = () => m_DefaultStyleNamespace,
+ IsInSelectionRange = (r, c) => IsInSelectionRange(r, c),
+ GetSelectedRow = () => SelectionHandle.StartRow,
+ GetSelectedCol = () => SelectionHandle.StartCol,
+ };
+ return renderer;
+ }
+
+ private void DestroyFrozenRenderers()
+ {
+ m_FrozenColumnRenderer?.DestroyAll();
+ m_FrozenRowRenderer?.DestroyAll();
+ m_FrozenCornerRenderer?.DestroyAll();
+ DestroyFrozenBorderRenderers();
+ m_FrozenColumnRenderer = null;
+ m_FrozenRowRenderer = null;
+ m_FrozenCornerRenderer = null;
+ DestroyFrozenContainer(ref m_FrozenColumnsContainer);
+ DestroyFrozenContainer(ref m_FrozenRowsContainer);
+ DestroyFrozenContainer(ref m_FrozenCornerContainer);
+ }
+
+ private static void DestroyFrozenContainer(ref RectTransform container)
+ {
+ if (container == null) return;
+ if (Application.isPlaying) Object.Destroy(container.gameObject);
+ else Object.DestroyImmediate(container.gameObject);
+ container = null;
+ }
+
+ private void ReleaseFrozenCells()
+ {
+ m_FrozenColumnRenderer?.ReleaseAll();
+ m_FrozenRowRenderer?.ReleaseAll();
+ m_FrozenCornerRenderer?.ReleaseAll();
+ }
+
+ private void RenderFrozenCells(bool fullRefresh = true)
+ {
+ int rows = m_RowHeights?.Length ?? 0;
+ int cols = m_ColWidths?.Length ?? 0;
+ int freezeRows = Mathf.Clamp(m_FreezeRowCount, 0, rows);
+ int freezeCols = Mathf.Clamp(m_FreezeColCount, 0, cols);
+ if (freezeRows == 0 && freezeCols == 0)
+ {
+ DestroyFrozenRenderers();
return;
}
- // 检测冻结范围变更,重建渲染器
- if (m_ViewportRenderer == null ||
- m_ViewportRenderer.EndRow != m_FreezeRowCount ||
- m_ViewportRenderer.EndCol != m_FreezeColCount)
- CreateViewportRenderer();
-
- // 计算冻结区域尺寸
- float freezeW = 0, freezeH = 0;
- for (int c = 0; c < m_FreezeColCount && c < m_ColWidths.Length; c++)
- freezeW += m_ColWidths[c];
- for (int r = 0; r < m_FreezeRowCount && r < m_RowHeights.Length; r++)
- freezeH += m_RowHeights[r];
-
- // 创建/更新背景
- m_ViewportRenderer.CreateBackground(freezeW, freezeH, m_DefaultStyleNamespace);
-
- // 获取滚动偏移
- Vector2 scrollOffset = Vector2.zero;
- if (m_ScrollRect != null && m_ScrollRect.content != null)
- {
- scrollOffset.x = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.x);
- scrollOffset.y = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.y);
- }
-
- // 调整背景位置(随滚动偏移移动,保持在视口可见范围内)
- if (m_ViewportRenderer.BackgroundGo != null)
- {
- var bgRt = m_ViewportRenderer.BackgroundGo.GetComponent();
- bgRt.anchoredPosition = new Vector2(scrollOffset.x, -scrollOffset.y);
- }
-
- // 回收并重新渲染冻结单元格
- m_ViewportRenderer.ReturnAll();
- m_ViewportRenderer.RenderCells(
- 0, m_FreezeRowCount,
- 0, m_FreezeColCount,
- !Application.isPlaying);
-
- // 冻结单元格需要跟随滚动偏移(相对于 Viewport 定位)
- if (m_ViewportRenderer.Assembler != null)
- {
- // 调整所有活跃的池对象的 anchoredPosition 以抵消滚动
- // 这通过重新定位背景实现,单元格相对于背景 (0,0) 定位,所以随背景一起移动
- }
-
- // 背景始终可见
- m_ViewportRenderer.BackgroundGo?.SetActive(true);
+ if (m_FrozenColumnRenderer == null) CreateViewportRenderer();
+ if (m_FrozenColumnRenderer == null) return;
+ CreateFrozenBackgrounds(freezeRows, freezeCols);
+ SyncFrozenContainers();
+ CalcVisibleRange(out int visibleStartRow, out int visibleEndRow, out int visibleStartCol, out int visibleEndCol);
+#if UNITY_EDITOR
+ bool editorMode = !Application.isPlaying;
+#else
+ bool editorMode = false;
+#endif
+ // 固定顺序:Content 已先完成,然后冻结列、冻结行、左上交叉区。
+ if (freezeCols > 0)
+ m_FrozenColumnRenderer.Render(Mathf.Max(freezeRows, visibleStartRow), visibleEndRow,
+ 0, freezeCols, editorMode, fullRefresh);
+ if (freezeRows > 0)
+ m_FrozenRowRenderer.Render(0, freezeRows, Mathf.Max(freezeCols, visibleStartCol), visibleEndCol,
+ editorMode, fullRefresh);
+ if (freezeRows > 0 && freezeCols > 0)
+ m_FrozenCornerRenderer.Render(0, freezeRows, 0, freezeCols, editorMode, fullRefresh);
}
- #endregion
+ private void CreateFrozenBackgrounds(int freezeRows, int freezeCols)
+ {
+ float freezeWidth = GetColLeftX(freezeCols);
+ float freezeHeight = GetRowTopY(freezeRows);
+ if (freezeCols > 0)
+ m_FrozenColumnRenderer.CreateBackground(freezeWidth, m_TotalHeight, m_DefaultStyleNamespace);
+ if (freezeRows > 0)
+ m_FrozenRowRenderer.CreateBackground(m_TotalWidth - freezeWidth, freezeHeight,
+ m_DefaultStyleNamespace, new Vector2(freezeWidth, 0f));
+ if (freezeRows > 0 && freezeCols > 0)
+ m_FrozenCornerRenderer.CreateBackground(freezeWidth, freezeHeight, m_DefaultStyleNamespace);
+ }
+
+ private void SyncFrozenContainers()
+ {
+ if (m_ScrollRect == null || m_ScrollRect.content == null) return;
+ Vector2 contentPosition = m_ScrollRect.content.anchoredPosition;
+ SyncFrozenContainer(m_FrozenColumnsContainer, new Vector2(0f, contentPosition.y));
+ SyncFrozenContainer(m_FrozenRowsContainer, new Vector2(contentPosition.x, 0f));
+ SyncFrozenContainer(m_FrozenCornerContainer, Vector2.zero);
+ }
+
+ private void SyncFrozenContainer(RectTransform container, Vector2 position)
+ {
+ if (container == null) return;
+ container.anchoredPosition = position;
+ container.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
+ }
}
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
index 5a80526..b4d643d 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
@@ -78,6 +78,7 @@ namespace XericUI.XTable.Rendering.Component
foreach (var cell in q.Cells)
{
if (!TableData.IsCellMerged(cell.Item1, cell.Item2)
+ && IsNormalRegionCell(cell.Item1, cell.Item2)
&& IsCellVisible(cell.Item1, cell.Item2))
result.Add(cell);
}
@@ -89,7 +90,7 @@ namespace XericUI.XTable.Rendering.Component
for (int r = sr; r < er; r++)
for (int c = sc; c < ec; c++)
{
- if (!TableData.IsCellMerged(r, c) && IsCellVisible(r, c))
+ if (!TableData.IsCellMerged(r, c) && IsNormalRegionCell(r, c) && IsCellVisible(r, c))
result.Add((r, c));
}
}
@@ -103,7 +104,7 @@ namespace XericUI.XTable.Rendering.Component
for (int r = sr; r < er; r++)
for (int c = sc; c < ec; c++)
{
- if (!TableData.IsCellMerged(r, c) && IsCellVisible(r, c))
+ if (!TableData.IsCellMerged(r, c) && IsNormalRegionCell(r, c) && IsCellVisible(r, c))
result.Add((r, c));
}
}
@@ -312,6 +313,7 @@ namespace XericUI.XTable.Rendering.Component
}
m_PrefabPool?.ReturnAll();
ReleaseAllTrackedCells();
+ ReleaseFrozenCells();
// 清理孤立子对象
CleanOrphanChildren();
@@ -327,9 +329,9 @@ namespace XericUI.XTable.Rendering.Component
foreach (var (r, c) in m_LastRenderedCells)
RenderCellContent(r, c, ns, editorMode);
- // 边框 + 冻结
+ // 主 Content 已先渲染,冻结区随后复用其释放的对象;边框按区域最后提交。
+ RenderFrozenCells(true);
RebuildBorders();
- RenderFrozenCells();
m_DirtyFlag.Clear();
OnTableRefreshed?.Invoke();
@@ -410,6 +412,9 @@ namespace XericUI.XTable.Rendering.Component
RenderCellContent(cell.Item1, cell.Item2, ns, editorMode);
}
+ // 冻结列、冻结行与左上角在 Content 后更新,并复用同一全局对象池。
+ RenderFrozenCells(false);
+
// 5. 交换:将 newVisible 作为下一帧的比较基准
(m_LastRenderedCells, newVisible) = (newVisible, m_LastRenderedCells);
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
index d88fd5d..5ba26d2 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
@@ -60,8 +60,13 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private CellAssembler m_Assembler;
[System.NonSerialized] private XTableDirtyFlag m_DirtyFlag;
- /// Viewport 层渲染器——用于绘制冻结行列单元格
- [System.NonSerialized] private TableRenderer m_ViewportRenderer;
+ /// Viewport 下的冻结列、冻结行与左上交叉区渲染器。
+ [System.NonSerialized] private TableRenderer m_FrozenColumnRenderer;
+ [System.NonSerialized] private TableRenderer m_FrozenRowRenderer;
+ [System.NonSerialized] private TableRenderer m_FrozenCornerRenderer;
+ [System.NonSerialized] private RectTransform m_FrozenColumnsContainer;
+ [System.NonSerialized] private RectTransform m_FrozenRowsContainer;
+ [System.NonSerialized] private RectTransform m_FrozenCornerContainer;
[System.NonSerialized] private XTableSelectionHandle m_SelectionHandle;
@@ -82,8 +87,11 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private readonly HashSet m_ActiveStyleNamespaces = new();
[System.NonSerialized] private bool m_StyleNamespaceDirty = true;
- /// 边框线段渲染器——使用 UILineRendererV2 的坐标线段绘制
+ /// 各渲染区域的边框线段渲染器。
[System.NonSerialized] private UILineRendererV2 m_BorderRenderer;
+ [System.NonSerialized] private UILineRendererV2 m_FrozenColumnBorderRenderer;
+ [System.NonSerialized] private UILineRendererV2 m_FrozenRowBorderRenderer;
+ [System.NonSerialized] private UILineRendererV2 m_FrozenCornerBorderRenderer;
private float m_TotalWidth;
private float m_TotalHeight;
@@ -309,9 +317,7 @@ namespace XericUI.XTable.Rendering.Component
DestroyBorderRenderer();
DestroyBackgroundRenderer();
- // 销毁 Viewport 冻结渲染器
- m_ViewportRenderer?.DestroyAll();
- m_ViewportRenderer = null;
+ DestroyFrozenRenderers();
base.OnDestroy();
}
diff --git a/Runtime/XTable/Rendering/Elements/CellAssembler.cs b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
index bfa90db..179fdc6 100644
--- a/Runtime/XTable/Rendering/Elements/CellAssembler.cs
+++ b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
@@ -96,11 +96,14 @@ namespace XericUI.XTable.Rendering.Elements
#region 池操作
/// 为指定 cellId 获取一个池化 Image
- public PooledImage GetImage(int cellId, string name = "cell_img", string styleNamespace = null)
+ public PooledImage GetImage(int cellId, string name = "cell_img", string styleNamespace = null,
+ Transform parent = null)
{
var item = m_ImagePool.Acquire(cellId);
if (item == null) return null;
+ if (parent != null && item.Root.transform.parent != parent)
+ item.Root.transform.SetParent(parent, false);
item.CellId = cellId;
item.Root.name = name;
ResetImageDefaults(item);
@@ -109,11 +112,14 @@ namespace XericUI.XTable.Rendering.Elements
}
/// 为指定 cellId 获取一个池化 TMP_Text
- public PooledText GetText(int cellId, string name = "cell_text", string styleNamespace = null)
+ public PooledText GetText(int cellId, string name = "cell_text", string styleNamespace = null,
+ Transform parent = null)
{
var item = m_TextPool.Acquire(cellId);
if (item == null) return null;
+ if (parent != null && item.Root.transform.parent != parent)
+ item.Root.transform.SetParent(parent, false);
item.CellId = cellId;
item.Root.name = name;
ResetTextDefaults(item);
@@ -155,7 +161,7 @@ namespace XericUI.XTable.Rendering.Elements
{
if (item == null) return;
item.Style?.Unbind();
- m_ImagePool.Release(item.CellId);
+ m_ImagePool.ReleaseByObject(item);
}
/// 归还单个 Text 池对象(通过 CellId 定位槽位并移入 FrozenSlots)
@@ -163,7 +169,7 @@ namespace XericUI.XTable.Rendering.Elements
{
if (item == null) return;
item.Style?.Unbind();
- m_TextPool.Release(item.CellId);
+ m_TextPool.ReleaseByObject(item);
}
#endregion
diff --git a/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
index 436aba7..95d1f7a 100644
--- a/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
+++ b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
@@ -75,15 +75,20 @@ namespace XericUI.XTable.Rendering.Elements
/// 为指定 cellId 获取预制体实例。
/// 若该 cellId 已有绑定的实例,直接返回;否则创建新实例(需未达上限)。
///
- public GameObject Acquire(GameObject prefab, int cellId)
+ public GameObject Acquire(GameObject prefab, int cellId, Transform parent = null)
{
if (prefab == null) return null;
var pool = GetOrCreatePool(prefab);
+ Transform targetParent = parent ?? m_Parent;
- // 已绑定 → 直接复用
+ // 已绑定 → 直接复用并迁移到当前渲染区域。
if (pool.Slots.TryGetValue(cellId, out var existing))
+ {
+ if (targetParent != null && existing.transform.parent != targetParent)
+ existing.transform.SetParent(targetParent, false);
return existing;
+ }
// 创建新实例
if (m_TotalCreated >= MAX_PREFAB_ITEMS)
@@ -92,7 +97,7 @@ namespace XericUI.XTable.Rendering.Elements
return null;
}
- var instance = Object.Instantiate(prefab, m_Parent);
+ var instance = Object.Instantiate(prefab, targetParent);
instance.hideFlags = POOL_FLAGS;
instance.name = $"_prefab_{cellId}";
m_TotalCreated++;
@@ -142,17 +147,9 @@ namespace XericUI.XTable.Rendering.Elements
KillObject(instance);
}
- /// 将所有活跃槽位清空并销毁对象
+ /// 结束当前渲染周期。实例保留在全局槽位中,以便跨区域复用。
public void ReturnAll()
{
- foreach (var kv in m_Pools)
- {
- var pool = kv.Value;
- foreach (var instance in pool.Slots.Values)
- KillObject(instance);
- pool.Slots.Clear();
- }
- m_TotalCreated = 0;
}
/// 获取当前所有活跃实例(用于外部遍历回调)
diff --git a/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs b/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
index 0d86346..efe7857 100644
--- a/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
+++ b/Runtime/XTable/Rendering/Elements/SlotCounterPool.cs
@@ -7,8 +7,8 @@ namespace XericUI.XTable.Rendering.Elements
{
///
/// 基于计数器的槽位对象池(泛型)。
- /// 每个 cellId 映射到固定索引的 SlotCounter,槽位在计数器间通过 FrozenSlots 流转复用。
- /// 避免 Queue 模式下对象与位置解耦导致的堆叠问题,同时不触发 SetActive/SetParent 等生命周期事件。
+ /// 每个 cellId 映射到固定索引的 SlotCounter,释放槽位保留在原计数器内复用。
+ /// 避免跨计数器拆分碰撞组导致的对象位置冲突。
///
/// 池对象类型
public class SlotCounterPool where T : class
@@ -37,9 +37,6 @@ namespace XericUI.XTable.Rendering.Elements
private SlotCounter[] m_Counters = new SlotCounter[POOL_SIZE];
- /// 冻结槽位列表——全局可偷取的休眠槽位(末尾偷取,避免 Canvas 重建)
- private List m_FrozenSlots = new List();
-
/// 已创建对象总数(不含预制体 Instantiate)
private int m_CreatedCount;
@@ -64,7 +61,7 @@ namespace XericUI.XTable.Rendering.Elements
///
/// 为指定 cellId 获取一个池对象。
- /// 优先复用本计数器内的空闲槽位,其次从 FrozenSlots 偷取,最后创建新对象。
+ /// 优先复用本计数器内的空闲槽位,最后创建新对象。
///
public T Acquire(int cellId)
{
@@ -88,18 +85,7 @@ namespace XericUI.XTable.Rendering.Elements
}
}
- // 2. 从 FrozenSlots 末尾偷取
- if (m_FrozenSlots.Count > 0)
- {
- int lastIdx = m_FrozenSlots.Count - 1;
- var stolen = m_FrozenSlots[lastIdx];
- m_FrozenSlots.RemoveAt(lastIdx);
- stolen.CellId = cellId;
- counter.Slots.Add(stolen);
- return stolen.PooledObject;
- }
-
- // 3. 创建新对象
+ // 2. 创建新对象。槽位不能跨计数器流转,避免破坏碰撞单元格的稳定关系。
if (m_CreatedCount < POOL_SIZE)
{
var obj = m_Factory();
@@ -115,7 +101,7 @@ namespace XericUI.XTable.Rendering.Elements
}
///
- /// 释放指定 cellId 占用的槽位,将其移入 FrozenSlots 供其他计数器复用。
+ /// 释放指定 cellId 占用的槽位,并保留在原计数器中供相同碰撞组复用。
///
public void Release(int cellId)
{
@@ -129,10 +115,7 @@ namespace XericUI.XTable.Rendering.Elements
{
if (counter.Slots[i].CellId == cellId)
{
- var slot = counter.Slots[i];
- counter.Slots.RemoveAt(i);
- slot.CellId = -1;
- m_FrozenSlots.Add(slot);
+ counter.Slots[i].CellId = -1;
return;
}
}
@@ -156,10 +139,7 @@ namespace XericUI.XTable.Rendering.Elements
{
if (ReferenceEquals(counter.Slots[i].PooledObject, pooledObject))
{
- var slot = counter.Slots[i];
- counter.Slots.RemoveAt(i);
- slot.CellId = -1;
- m_FrozenSlots.Add(slot);
+ counter.Slots[i].CellId = -1;
return true;
}
}
@@ -167,26 +147,20 @@ namespace XericUI.XTable.Rendering.Elements
return false;
}
- /// 将所有活跃槽位移入 FrozenSlots
+ /// 释放所有活跃槽位,但保留其所属计数器。
public void ReturnAll()
{
for (int ci = 0; ci < POOL_SIZE; ci++)
{
var counter = m_Counters[ci];
if (counter == null) continue;
-
- for (int i = counter.Slots.Count - 1; i >= 0; i--)
- {
- var slot = counter.Slots[i];
- counter.Slots.RemoveAt(i);
- slot.CellId = -1;
- m_FrozenSlots.Add(slot);
- }
+ for (int i = 0; i < counter.Slots.Count; i++)
+ counter.Slots[i].CellId = -1;
}
}
///
- /// 彻底销毁所有池对象(包括计数器内活跃槽位和 FrozenSlots 中的冻结槽位)。
+ /// 彻底销毁所有池对象。
///
public void DestroyAll(Action destroyFunc)
{
@@ -202,10 +176,6 @@ namespace XericUI.XTable.Rendering.Elements
counter.Slots.Clear();
}
- foreach (var slot in m_FrozenSlots)
- destroyFunc(slot.PooledObject);
- m_FrozenSlots.Clear();
-
m_CreatedCount = 0;
}