表格添加单元格冻结功能

This commit is contained in:
2026-07-09 11:40:19 +08:00
parent 664c2c841f
commit ff8f049e30
4 changed files with 575 additions and 0 deletions
@@ -44,6 +44,14 @@ namespace XericUI.XTable.Rendering.Component
[SerializeField] private ScrollRect m_ScrollRect;
[SerializeField]
[Tooltip("冻结行数——顶部固定不滚动的行数")]
private int m_FreezeRowCount;
[SerializeField]
[Tooltip("冻结列数——左侧固定不滚动的列数")]
private int m_FreezeColCount;
[SerializeField]
[Tooltip("在 Hierarchy 中显示动态生成的子对象(仅编辑器,不保存到场景)")]
private bool m_ChildrenVisible;
@@ -55,6 +63,9 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private CellAssembler m_Assembler;
[System.NonSerialized] private XTableDirtyFlag m_DirtyFlag;
/// <summary>Viewport 层渲染器——用于绘制冻结行列单元格</summary>
[System.NonSerialized] private TableRenderer m_ViewportRenderer;
[System.NonSerialized] private XTableSelectionHandle m_SelectionHandle;
[System.NonSerialized] private GameObject m_BackgroundGo;
@@ -105,6 +116,21 @@ namespace XericUI.XTable.Rendering.Component
public float[] RowHeights => m_RowHeights;
public float[] ColWidths => m_ColWidths;
/// <summary>冻结行数</summary>
public int FreezeRowCount => m_FreezeRowCount;
/// <summary>冻结列数</summary>
public int FreezeColCount => m_FreezeColCount;
/// <summary>设置冻结行列数并重建视图</summary>
public void SetFreezeCount(int rows, int cols)
{
m_FreezeRowCount = Mathf.Max(0, rows);
m_FreezeColCount = Mathf.Max(0, cols);
CreateViewportRenderer();
MarkDirty(TableDirtyType.LayoutChanged);
RefreshView();
}
public string DefaultStyleNamespace => m_DefaultStyleNamespace;
/// <summary>选中操作句柄——外部可通过此句柄控制选区,或直接操作 StartRow/StartCol 等属性</summary>
@@ -332,6 +358,9 @@ namespace XericUI.XTable.Rendering.Component
CreateDefaultSampleData();
CreateBackground();
// 创建 Viewport 层渲染器(用于冻结单元格)
CreateViewportRenderer();
if (m_ScrollRect != null)
m_ScrollRect.onValueChanged.AddListener(OnScrollValueChanged);
@@ -385,6 +414,10 @@ namespace XericUI.XTable.Rendering.Component
m_BackgroundGo = null;
}
// 销毁 Viewport 冻结渲染器
m_ViewportRenderer?.DestroyAll();
m_ViewportRenderer = null;
base.OnDestroy();
}
@@ -816,6 +849,9 @@ namespace XericUI.XTable.Rendering.Component
// 重建边框线段
RebuildBorders();
// 渲染 Viewport 层冻结单元格
RenderFrozenCells();
m_DirtyFlag.Clear();
OnTableRefreshed?.Invoke();
SetChildrenVisible(m_ChildrenVisible);
@@ -905,6 +941,7 @@ namespace XericUI.XTable.Rendering.Component
RecalculateTotalSize();
CreateDefaultSampleData();
CreateBackground();
CreateViewportRenderer();
MarkDirty(TableDirtyType.All);
RefreshView();
SetChildrenVisible(m_ChildrenVisible);
@@ -983,6 +1020,7 @@ namespace XericUI.XTable.Rendering.Component
MarkDirty(TableDirtyType.All);
RecalculateTotalSize();
CreateBackground();
CreateViewportRenderer();
RefreshView();
SetChildrenVisible(m_ChildrenVisible);
}
@@ -1597,6 +1635,105 @@ namespace XericUI.XTable.Rendering.Component
#endregion
#region Viewport 冻结渲染
/// <summary>创建或重新创建 Viewport 层冻结渲染器</summary>
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;
}
/// <summary>渲染 Viewport 层冻结单元格,随滚动偏移调整位置</summary>
private void RenderFrozenCells()
{
if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0)
{
// 无冻结需求时清理渲染器
if (m_ViewportRenderer != null)
{
m_ViewportRenderer.DestroyAll();
m_ViewportRenderer = null;
}
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<RectTransform>();
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);
}
#endregion
#region IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)