From cbae052e0f402bf0a99c57e5481b8ff76f89d2d5 Mon Sep 17 00:00:00 2001
From: LiRuoChen <571244399@qq.com>
Date: Thu, 9 Jul 2026 17:10:40 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=A0=B7=E5=BC=8F=E7=BB=84?=
=?UTF-8?q?=E4=BB=B6=E6=A0=B7=E5=BC=8F=E8=B7=AF=E5=BE=84=E7=9A=84=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E5=80=BC=E3=80=82=20=E9=92=88=E5=AF=B9=E8=A1=A8?=
=?UTF-8?q?=E6=A0=BC=E7=94=9F=E6=88=90=E9=A2=84=E5=88=B6=E4=BD=93=E7=9A=84?=
=?UTF-8?q?=E9=80=BB=E8=BE=91=E8=BF=9B=E8=A1=8C=E4=BC=98=E5=8C=96=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Editor/SuperStyleSheet.meta | 8 -
Editor/SuperStyleSheet/StyleFieldDrawer.cs | 275 ------------------
Editor/XTable/XericUIActionTableEditor.cs | 38 +++
Runtime/Core/StyleSheetUI/StyleSheetState.cs | 49 ++--
Runtime/XTable/Core/XTableCellData.cs | 77 ++---
.../Rendering/Component/TableRenderer.cs | 87 ++----
.../Rendering/Component/XericUIActionTable.cs | 164 ++++++-----
.../Rendering/Elements/PrefabPrototypePool.cs | 186 ++++++++++++
.../Elements/PrefabPrototypePool.cs.meta | 2 +-
9 files changed, 412 insertions(+), 474 deletions(-)
delete mode 100644 Editor/SuperStyleSheet.meta
delete mode 100644 Editor/SuperStyleSheet/StyleFieldDrawer.cs
create mode 100644 Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
rename Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta => Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs.meta (83%)
diff --git a/Editor/SuperStyleSheet.meta b/Editor/SuperStyleSheet.meta
deleted file mode 100644
index 668124b..0000000
--- a/Editor/SuperStyleSheet.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: 44c3980d2307e514a869459f8153cda2
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Editor/SuperStyleSheet/StyleFieldDrawer.cs b/Editor/SuperStyleSheet/StyleFieldDrawer.cs
deleted file mode 100644
index f5f1ecc..0000000
--- a/Editor/SuperStyleSheet/StyleFieldDrawer.cs
+++ /dev/null
@@ -1,275 +0,0 @@
-using UnityEditor;
-using UnityEngine;
-using XericLibrary.Runtime.SuperStyleSheet;
-
-namespace XericUIEditor
-{
- ///
- /// StyleField 的自定义 Inspector 属性绘制器。
- ///
- /// 默认折叠状态:仅显示样式路径下拉框。
- /// 展开状态:显示命名空间、路径、资源值类型(有值时)、注解描述(有注解时)。
- /// 无值/无注解的行不显示。
- ///
- [CustomPropertyDrawer(typeof(StyleField))]
- public class StyleFieldDrawer : PropertyDrawer
- {
- private const float LINE_HEIGHT = 18f;
- private const float PADDING = 2f;
- private const float FOLD_BUTTON_WIDTH = 14f;
-
- private const string PROP_NAMESPACE = "_namespace";
- private const string PROP_STYLEPATH = "_stylePath";
-
- /// 折叠状态(按 property path 缓存)
- private static readonly System.Collections.Generic.Dictionary s_FoldoutStates
- = new System.Collections.Generic.Dictionary();
-
- public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
- {
- var nsProp = property.FindPropertyRelative(PROP_NAMESPACE);
- var pathProp = property.FindPropertyRelative(PROP_STYLEPATH);
- if (nsProp == null || pathProp == null)
- return PADDING + (LINE_HEIGHT + PADDING);
-
- string key = GetFoldKey(property);
- bool expanded = s_FoldoutStates.TryGetValue(key, out bool f) && f;
-
- if (!expanded)
- return PADDING + (LINE_HEIGHT + PADDING);
-
- // 基础:命名空间 + 路径 = 2 行
- int rowCount = 2;
-
- // 有值才显示值类型行
- string ns = GetNamespace(property);
- string path = pathProp.stringValue;
- StyleValue val = StyleManager.GetValue(ns, path);
- if (val != null && val.ValueType != null)
- rowCount++;
-
- // 有注解才显示注解行
- var annotations = StyleManager.GetAnnotations(ns, path);
- if (annotations != null && annotations.Count > 0)
- rowCount++;
-
- return PADDING + (1 + rowCount) * (LINE_HEIGHT + PADDING);
- }
-
- public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
- {
- string key = GetFoldKey(property);
- bool expanded = s_FoldoutStates.TryGetValue(key, out bool fold) && fold;
-
- // 获取子属性
- SerializedProperty nsProp = property.FindPropertyRelative(PROP_NAMESPACE);
- SerializedProperty pathProp = property.FindPropertyRelative(PROP_STYLEPATH);
-
- if (nsProp == null || pathProp == null)
- {
- EditorGUI.LabelField(position, label != null ? label.text : "Style Field", " (未初始化)");
- return;
- }
-
- // === 标题行:折叠箭头 + 标签 + 路径下拉 ===
- Rect headerRect = new Rect(position.x, position.y + PADDING, position.width, LINE_HEIGHT);
-
- // 折叠按钮
- Rect foldRect = new Rect(headerRect.x, headerRect.y, FOLD_BUTTON_WIDTH, LINE_HEIGHT);
- bool newFold = EditorGUI.Foldout(foldRect, expanded, GUIContent.none, true);
- if (newFold != expanded)
- {
- s_FoldoutStates[key] = newFold;
- expanded = newFold;
- }
-
- // 标签
- Rect labelRect = new Rect(headerRect.x + FOLD_BUTTON_WIDTH, headerRect.y,
- EditorGUIUtility.labelWidth - FOLD_BUTTON_WIDTH, LINE_HEIGHT);
- EditorGUI.LabelField(labelRect, label);
-
- // 路径下拉框(始终可见)
- Rect pathRect = new Rect(headerRect.x + EditorGUIUtility.labelWidth + 4f, headerRect.y,
- headerRect.width - EditorGUIUtility.labelWidth - 8f, LINE_HEIGHT);
-
- string currentNs = GetNamespace(property);
-
- // 首次绘制时 xsss 可能尚未加载,当前命名空间若无效则自动切换
- EnsureValidNamespace(nsProp, ref currentNs);
-
- DrawPathDropdown(pathRect, pathProp, currentNs);
-
- if (!expanded)
- return;
-
- // === 展开区域 ===
- float y = headerRect.y + LINE_HEIGHT + PADDING;
- float indent = 16f;
- float fieldWidth = position.width - indent;
-
- // --- 命名空间 ---
- Rect nsLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
- Rect nsFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
- EditorGUI.LabelField(nsLabelRect, "命名空间");
- DrawNamespaceDropdown(nsFieldRect, nsProp);
- y += LINE_HEIGHT + PADDING;
-
- // --- 路径 ---
- Rect pathLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
- Rect pathFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
- EditorGUI.LabelField(pathLabelRect, "路径");
- DrawPathDropdown(pathFieldRect, pathProp, currentNs);
- y += LINE_HEIGHT + PADDING;
-
- // --- 资源值类型(仅在有值时显示) ---
- StyleValue currentValue = StyleManager.GetValue(currentNs, pathProp.stringValue);
- if (currentValue != null && currentValue.ValueType != null)
- {
- Rect valLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
- Rect valFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
-
- EditorGUI.LabelField(valLabelRect, "值类型");
-
- string typeDisplay = GetShortTypeName(currentValue.ValueType);
- string valuePreview = currentValue.RawValue != null
- ? currentValue.RawValue.ToString()
- : "";
-
- EditorGUI.LabelField(valFieldRect, $"{typeDisplay} = {valuePreview}");
- y += LINE_HEIGHT + PADDING;
- }
-
- // --- 注解描述(仅在有注解时显示) ---
- var annotations = StyleManager.GetAnnotations(currentNs, pathProp.stringValue);
- if (annotations != null && annotations.Count > 0)
- {
- Rect annoLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
- Rect annoFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
-
- EditorGUI.LabelField(annoLabelRect, "注解");
-
- // 优先显示 @Desc
- annotations.TryGetValue("Desc", out string desc);
- if (!string.IsNullOrEmpty(desc))
- EditorGUI.LabelField(annoFieldRect, desc);
- else
- {
- foreach (var a in annotations)
- {
- EditorGUI.LabelField(annoFieldRect, $"@{a.Key}: {a.Value}");
- break;
- }
- }
- }
-
- // 变更检测
- if (GUI.changed)
- {
- property.serializedObject.ApplyModifiedProperties();
- }
- }
-
- #region 下拉框绘制
-
- private void DrawNamespaceDropdown(Rect rect, SerializedProperty nsProp)
- {
- var namespaces = StyleManager.GetAvailableNamespaces();
- if (namespaces.Count == 0)
- namespaces.Add("default");
-
- int currentIndex = namespaces.IndexOf(nsProp.stringValue);
- if (currentIndex < 0) currentIndex = 0;
-
- int newIndex = EditorGUI.Popup(rect, currentIndex, namespaces.ToArray());
- if (newIndex >= 0 && newIndex < namespaces.Count)
- {
- nsProp.stringValue = namespaces[newIndex];
- }
- }
-
- private void DrawPathDropdown(Rect rect, SerializedProperty pathProp, string namespace_)
- {
- var paths = StyleManager.GetPathsForNamespace(namespace_);
-
- var displayOptions = new System.Collections.Generic.List();
- if (paths.Count == 0)
- {
- displayOptions.Add("(无可用路径)");
- }
- else
- {
- foreach (var p in paths)
- {
- StyleValue val = StyleManager.GetValue(namespace_, p);
- string typeStr = val?.ValueType != null ? GetShortTypeName(val.ValueType) : "?";
- string valStr = val?.RawValue != null ? val.RawValue.ToString() : "";
- if (valStr.Length > 20) valStr = valStr.Substring(0, 17) + "...";
- displayOptions.Add($"{p} [{typeStr}] {valStr}");
- }
- }
-
- int currentIndex = System.Math.Max(0, paths.IndexOf(pathProp.stringValue));
-
- int newIndex = EditorGUI.Popup(rect, currentIndex, displayOptions.ToArray());
- if (newIndex >= 0 && newIndex < paths.Count)
- {
- pathProp.stringValue = paths[newIndex];
- }
- }
-
- #endregion
-
- #region 工具方法
-
- private static string GetFoldKey(SerializedProperty property)
- {
- return $"{property.serializedObject.targetObject.GetInstanceID()}_{property.propertyPath}";
- }
-
- private static string GetNamespace(SerializedProperty property)
- {
- var nsProp = property.FindPropertyRelative(PROP_NAMESPACE);
- string ns = nsProp?.stringValue;
- return string.IsNullOrEmpty(ns) ? "default" : ns;
- }
-
- private static string GetShortTypeName(System.Type type)
- {
- if (type == null) return "?";
- switch (type.Name)
- {
- case "Single": return "float";
- case "Int32": return "int";
- case "String": return "string";
- case "Boolean": return "bool";
- default: return type.Name;
- }
- }
-
- ///
- /// 若当前命名空间无可用路径(xsss 尚未加载),自动切换至首个有路径的命名空间。
- ///
- private static void EnsureValidNamespace(SerializedProperty nsProp, ref string currentNs)
- {
- var paths = StyleManager.GetPathsForNamespace(currentNs);
- if (paths.Count > 0)
- return;
-
- // 当前命名空间不可用,尝试其他命名空间
- var allNs = StyleManager.GetAvailableNamespaces();
- foreach (var ns in allNs)
- {
- if (ns == currentNs) continue;
- var altPaths = StyleManager.GetPathsForNamespace(ns);
- if (altPaths.Count > 0)
- {
- currentNs = ns;
- nsProp.stringValue = ns;
- return;
- }
- }
- }
-
- #endregion
- }
-}
diff --git a/Editor/XTable/XericUIActionTableEditor.cs b/Editor/XTable/XericUIActionTableEditor.cs
index 1d2d89f..af00346 100644
--- a/Editor/XTable/XericUIActionTableEditor.cs
+++ b/Editor/XTable/XericUIActionTableEditor.cs
@@ -177,6 +177,44 @@ namespace XericUIEditor.XTable
if (GUILayout.Button("手动刷新视图", GUILayout.Height(22)))
EditorApplication.delayCall += () => m_Table.RefreshView();
+ // ── 预制体配置 ──
+ EditorGUILayout.Space(4);
+ EditorGUILayout.LabelField("单元格预制体配置", EditorStyles.boldLabel);
+ int selRow = m_Table.SelectedRow;
+ int selCol = m_Table.SelectedCol;
+ if (selRow < 0 || selCol < 0)
+ {
+ EditorGUILayout.HelpBox("请先在 Scene View 中点击选中一个单元格", MessageType.Info);
+ }
+ else
+ {
+ var data = m_Table.TableData?.GetCell(selRow, selCol);
+ GameObject currentPrefab = data?.Prefab;
+ EditorGUI.BeginDisabledGroup(true);
+ EditorGUILayout.TextField("目标单元格", $"({selRow}, {selCol})");
+ EditorGUI.EndDisabledGroup();
+
+ GameObject newPrefab = (GameObject)EditorGUILayout.ObjectField(
+ "预制体", currentPrefab, typeof(GameObject), false);
+
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("应用预制体", GUILayout.Height(22)))
+ {
+ Undo.RecordObject(m_Table, "Set Cell Prefab");
+ m_Table.SetCellPrefab(selRow, selCol, newPrefab);
+ EditorUtility.SetDirty(m_Table);
+ Repaint();
+ }
+ if (GUILayout.Button("清除预制体", GUILayout.Height(22)))
+ {
+ Undo.RecordObject(m_Table, "Clear Cell Prefab");
+ m_Table.ClearCellPrefab(selRow, selCol);
+ EditorUtility.SetDirty(m_Table);
+ Repaint();
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+
EditorGUI.indentLevel--;
}
diff --git a/Runtime/Core/StyleSheetUI/StyleSheetState.cs b/Runtime/Core/StyleSheetUI/StyleSheetState.cs
index 30d0ef1..840e61e 100644
--- a/Runtime/Core/StyleSheetUI/StyleSheetState.cs
+++ b/Runtime/Core/StyleSheetUI/StyleSheetState.cs
@@ -1,7 +1,7 @@
using System;
using UnityEngine;
-
+using UnityEngine.UI;
using XericLibrary.Runtime.SuperStyleSheet;
namespace XericUI.Core.StyleSheetUI
@@ -13,31 +13,31 @@ namespace XericUI.Core.StyleSheetUI
[Serializable]
public class StyleSheetState
{
- private const string k_DefaultColorBasePath = "/button/TargetGraphic/ColorTint";
- private const string k_DefaultTextureBasePath = "/button/TargetGraphic/SpriteState";
+ private const string k_DefaultColorBasePath = "/TargetGraphic/ColorTint";
+ private const string k_DefaultTextureBasePath = "/TargetGraphic/SpriteState";
private const string k_DefaultNamespace = "default";
#region 序列化字段
[SerializeField]
[Tooltip("正常状态")]
- private StyleField m_Normal = new StyleField { Namespace = k_DefaultNamespace, StylePath = k_DefaultColorBasePath + "/NormalColor" };
+ private StyleField m_Normal;
[SerializeField]
[Tooltip("高亮状态")]
- private StyleField m_Highlighted = new StyleField { Namespace = k_DefaultNamespace, StylePath = k_DefaultColorBasePath + "/HighlightedColor" };
+ private StyleField m_Highlighted;
[SerializeField]
[Tooltip("按下状态")]
- private StyleField m_Pressed = new StyleField { Namespace = k_DefaultNamespace, StylePath = k_DefaultColorBasePath + "/PressedColor" };
+ private StyleField m_Pressed;
[SerializeField]
[Tooltip("选中状态")]
- private StyleField m_Selected = new StyleField { Namespace = k_DefaultNamespace, StylePath = k_DefaultColorBasePath + "/SelectedColor" };
+ private StyleField m_Selected;
[SerializeField]
[Tooltip("禁用状态")]
- private StyleField m_Disabled = new StyleField { Namespace = k_DefaultNamespace, StylePath = k_DefaultColorBasePath + "/DisabledColor" };
+ private StyleField m_Disabled;
[SerializeField]
[Range(0.001f, 1f)]
@@ -46,6 +46,22 @@ namespace XericUI.Core.StyleSheetUI
#endregion
+ public StyleSheetState(string stylePathRoot, Selectable.Transition mode)
+ {
+ string state = mode switch
+ {
+ Selectable.Transition.ColorTint => "ColorTint",
+ Selectable.Transition.SpriteSwap => "SpriteState",
+ Selectable.Transition.Animation => "Animation",
+ _ => "None"
+ };
+ m_Normal = new StyleField($"{stylePathRoot}/TargetGraphic/{state}", k_DefaultNamespace);
+ m_Highlighted = new StyleField($"{stylePathRoot}/TargetGraphic/{state}", k_DefaultNamespace);
+ m_Pressed = new StyleField($"{stylePathRoot}/TargetGraphic/{state}", k_DefaultNamespace);
+ m_Selected = new StyleField($"{stylePathRoot}/TargetGraphic/{state}", k_DefaultNamespace);
+ m_Disabled = new StyleField($"{stylePathRoot}/TargetGraphic/{state}", k_DefaultNamespace);
+ }
+
#region 公开属性
public StyleField Normal => m_Normal;
@@ -67,22 +83,7 @@ namespace XericUI.Core.StyleSheetUI
};
/// 默认实例(五态默认匹配 ColorTint 颜色块)
- public static StyleSheetState Default => new StyleSheetState();
-
- ///
- /// 创建纹理五态样式块,StylePath 默认匹配 SpriteState 块。
- ///
- public static StyleSheetState CreateTexture(string ns = k_DefaultNamespace)
- {
- var state = new StyleSheetState();
- state.Normal.StylePath = k_DefaultTextureBasePath + "/NormalSprite";
- state.Highlighted.StylePath = k_DefaultTextureBasePath + "/HighlightedSprite";
- state.Pressed.StylePath = k_DefaultTextureBasePath + "/PressedSprite";
- state.Selected.StylePath = k_DefaultTextureBasePath + "/SelectedSprite";
- state.Disabled.StylePath = k_DefaultTextureBasePath + "/DisabledSprite";
- foreach (var f in state.AllFields) f.Namespace = ns;
- return state;
- }
+ public static StyleSheetState Default => new StyleSheetState("button", Selectable.Transition.ColorTint);
#endregion
diff --git a/Runtime/XTable/Core/XTableCellData.cs b/Runtime/XTable/Core/XTableCellData.cs
index 00dc7db..a5257fb 100644
--- a/Runtime/XTable/Core/XTableCellData.cs
+++ b/Runtime/XTable/Core/XTableCellData.cs
@@ -1,48 +1,53 @@
using System;
-
using UnityEngine;
namespace XericUI.XTable.Core
{
- ///
- /// 单元格数据模型——仅保存数据内容,不保存自身尺寸。
- /// 尺寸由渲染阶段的行列标题(行高/列宽)决定。
- /// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。
- ///
- [Serializable]
- public class XTableCellData
- {
- /// 文本内容
- public string Text;
+ ///
+ /// 单元格数据模型——仅保存数据内容,不保存自身尺寸。
+ /// 尺寸由渲染阶段的行列标题(行高/列宽)决定。
+ /// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。
+ ///
+ [Serializable]
+ public class XTableCellData
+ {
+ /// 文本内容
+ public string Text;
- /// 图片纹理
- public Texture2D Image;
+ /// 图片纹理
+ public Texture2D Image;
- /// 预制体对象(用于嵌入复杂 UI 元素)
- public GameObject Prefab;
+ /// 预制体对象(用于嵌入复杂 UI 元素)
+ public GameObject Prefab;
- ///
- /// 样式命名空间——对应 StyleManager 中的命名空间。
- /// 为空或 "xeric_table_default" 时使用默认表格样式。
- /// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。
- ///
- public string StyleNamespace;
+ ///
+ /// 样式命名空间——对应 StyleManager 中的命名空间。
+ /// 为空或 "xeric_table_default" 时使用默认表格样式。
+ /// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。
+ ///
+ public string StyleNamespace;
- public XTableCellData() { }
+ /// 预制体实例从对象池取出时回调——外部可在此初始化实例内容
+ [NonSerialized] public System.Action OnPrefabAcquire;
- public XTableCellData(string text)
- {
- Text = text;
- }
+ /// 预制体实例归还对象池前回调——外部可在此清理引用
+ [NonSerialized] public System.Action OnPrefabRelease;
- public XTableCellData(string text, string styleNamespace = null) : this(text)
- {
- StyleNamespace = styleNamespace;
- }
+ public XTableCellData()
+ { }
- public XTableCellData(string text, Texture2D image, string styleNamespace = null) : this(text, styleNamespace)
- {
- Image = image;
- }
- }
-}
+ 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;
+
+ public void ClearEvent()
+ {
+ OnPrefabAcquire = null;
+ OnPrefabRelease = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs
index 315300c..35a7c03 100644
--- a/Runtime/XTable/Rendering/Component/TableRenderer.cs
+++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs
@@ -17,11 +17,14 @@ namespace XericUI.XTable.Rendering.Component
#region 内部字段
private Transform m_Parent;
- private CellAssembler m_Assembler;
- private GameObject m_BackgroundGo;
+ private CellAssembler m_Assembler;
+ private GameObject m_BackgroundGo;
- /// 当前渲染器中所有预制体实例的缓存
- private Dictionary<(int, int), GameObject> m_PrefabInstances;
+ /// 共享预制体对象池引用(由 XericUIActionTable 传入)
+ public PrefabPrototypePool PrefabPool;
+
+ /// 共享活跃预制体映射表(由 XericUIActionTable 传入)
+ public Dictionary<(int, int), GameObject> ActivePrefabMap;
#endregion
@@ -88,7 +91,6 @@ namespace XericUI.XTable.Rendering.Component
public void DestroyAll()
{
m_Assembler?.DestroyAll();
- ClearPrefabInstances();
if (m_BackgroundGo != null)
{
@@ -262,8 +264,16 @@ namespace XericUI.XTable.Rendering.Component
XericUIActionTable.STYLE_CELL_FONT_ASSET) ?? txt.Text.font;
}
- if (data.Prefab != null)
- PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
+ if (data.Prefab != null && PrefabPool != null)
+ {
+ GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
+ 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);
+ }
}
}
}
@@ -336,65 +346,22 @@ namespace XericUI.XTable.Rendering.Component
txt.Text.text = data.Text;
}
- if (data.Prefab != null)
- PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
+ if (data.Prefab != null && PrefabPool != null)
+ {
+ GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
+ 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);
+ }
}
}
}
#endregion
- #region 预制体管理
-
- 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 = Object.Instantiate(prefab, m_Parent);
- instance.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
- instance.name = $"_prefab_{row}_{col}";
- m_PrefabInstances[key] = instance;
- return instance;
- }
-
- 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();
- if (prt == null) prt = instance.AddComponent();
-
- 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);
- }
-
- /// 销毁所有缓存预制体实例
- public void ClearPrefabInstances()
- {
- if (m_PrefabInstances == null) return;
- foreach (var kv in m_PrefabInstances)
- {
- if (kv.Value != null)
- {
- if (Application.isPlaying) Object.Destroy(kv.Value);
- else Object.DestroyImmediate(kv.Value);
- }
- }
- m_PrefabInstances.Clear();
- }
-
- #endregion
-
#region 坐标计算
private float CalcRowTopY(int row, float[] rowHeights)
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
index ccd9f0d..8940bac 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
@@ -70,8 +70,11 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private GameObject m_BackgroundGo;
- /// 预制体实例缓存,key 为 (row, col),用于复用已实例化的预制体
- [System.NonSerialized] private Dictionary<(int, int), GameObject> m_PrefabInstances;
+ /// 预制体对象池——按原型分组管理
+ [System.NonSerialized] private PrefabPrototypePool m_PrefabPool;
+
+ /// 活跃预制体实例映射 (row, col) → instance,用于回调编排
+ [System.NonSerialized] private Dictionary<(int, int), GameObject> m_ActivePrefabMap;
/// 表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限
[System.NonSerialized] private XTableQuadtree m_Quadtree;
@@ -260,7 +263,14 @@ namespace XericUI.XTable.Rendering.Component
}
if (data.Prefab != null)
- PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
+ {
+ GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
+ RectTransform prt = instance.GetComponent();
+ prt.anchoredPosition = new Vector2(cellX, cellY);
+ prt.sizeDelta = new Vector2(cellW, cellH);
+ m_ActivePrefabMap[(row, col)] = instance;
+ data.OnPrefabAcquire?.Invoke(instance);
+ }
}
}
}
@@ -332,7 +342,14 @@ namespace XericUI.XTable.Rendering.Component
}
if (data.Prefab != null)
- PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
+ {
+ GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
+ RectTransform prt = instance.GetComponent();
+ prt.anchoredPosition = new Vector2(cellX, cellY);
+ prt.sizeDelta = new Vector2(cellW, cellH);
+ m_ActivePrefabMap[(row, col)] = instance;
+ data.OnPrefabAcquire?.Invoke(instance);
+ }
}
}
}
@@ -353,6 +370,8 @@ namespace XericUI.XTable.Rendering.Component
m_TableData = new XTableData();
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ m_PrefabPool = new PrefabPrototypePool(ContentTransform);
+ m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
RecalculateTotalSize();
EnsureDefaultStyleNamespace();
CreateDefaultSampleData();
@@ -405,7 +424,8 @@ namespace XericUI.XTable.Rendering.Component
// 销毁所有动态生成的对象
if (m_Assembler != null)
m_Assembler.DestroyAll();
- ClearPrefabInstances();
+ m_PrefabPool?.DestroyAll();
+ m_ActivePrefabMap?.Clear();
DestroyBorderRenderer();
if (m_BackgroundGo != null)
{
@@ -497,15 +517,36 @@ namespace XericUI.XTable.Rendering.Component
MarkDirty(TableDirtyType.DataChanged);
}
- public void SetCellText(int row, int col, string text)
+ public XTableCellData SetCellText(int row, int col, string text)
{
var data = TableData.GetOrCreateCell(row, col);
data.Text = text;
MarkDirty(TableDirtyType.DataChanged);
+ return data;
}
public XTableCellData GetCellData(int row, int col)
- => TableData.GetOrCreateCell(row, col);
+ => TableData.GetOrCreateCell(row, col);
+
+ /// 为指定单元格设置预制体
+ public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ data.Prefab = prefab;
+ MarkDirty(TableDirtyType.DataChanged);
+ return data;
+ }
+
+ /// 清除指定单元格的预制体
+ public void ClearCellPrefab(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ if (data == null) return;
+ data.Prefab = null;
+ data.OnPrefabAcquire = null;
+ data.OnPrefabRelease = null;
+ MarkDirty(TableDirtyType.DataChanged);
+ }
/// 设置单元格的样式命名空间
public void SetCellStyleNamespace(int row, int col, string styleNamespace)
@@ -808,19 +849,32 @@ namespace XericUI.XTable.Rendering.Component
bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
}
- // 清理上一次渲染的预制体实例
- ClearPrefabInstances();
+ // 归还预制体前,触发所有活跃实例的释放回调
+ if (m_ActivePrefabMap != null)
+ {
+ foreach (var kv in m_ActivePrefabMap)
+ {
+ var data = TableData.GetCell(kv.Key.Item1, kv.Key.Item2);
+ data?.OnPrefabRelease?.Invoke(kv.Value);
+ }
+ m_ActivePrefabMap.Clear();
+ }
- // 回收池
+ // 回收预制体池
+ m_PrefabPool?.ReturnAll();
+
+ // 回收 CellAssembler 池
m_Assembler.ReturnAll();
// 额外清理孤立的直接子对象(防止域重载或异常流程产生的残留)
Transform poolRoot = m_Assembler.PoolRoot;
+ Transform prefabPoolRoot = m_PrefabPool?.PoolRoot;
Transform contentT = ContentTransform;
for (int i = contentT.childCount - 1; i >= 0; i--)
{
var child = contentT.GetChild(i);
- if ((poolRoot != null && child == poolRoot)) continue;
+ if (poolRoot != null && child == poolRoot) continue;
+ if (prefabPoolRoot != null && child == prefabPoolRoot) continue;
if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue;
child.gameObject.hideFlags = HideFlags.None;
@@ -931,6 +985,7 @@ namespace XericUI.XTable.Rendering.Component
public void FullRebuild()
{
m_Assembler?.DestroyAll();
+ m_PrefabPool?.DestroyAll();
ClearAllChildren();
m_BackgroundGo = null;
m_BorderRenderer = null;
@@ -938,6 +993,8 @@ namespace XericUI.XTable.Rendering.Component
m_TableData = new XTableData();
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ m_PrefabPool = new PrefabPrototypePool(ContentTransform);
+ m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
RecalculateTotalSize();
CreateDefaultSampleData();
CreateBackground();
@@ -992,7 +1049,8 @@ namespace XericUI.XTable.Rendering.Component
m_TableData.ClearAllMerges();
m_TableData.BlockMap.Clear();
}
- ClearPrefabInstances();
+ m_ActivePrefabMap?.Clear();
+ m_PrefabPool?.ReturnAll();
m_Quadtree?.Clear();
ClearSelection();
MarkDirty(TableDirtyType.DataChanged);
@@ -1005,7 +1063,8 @@ namespace XericUI.XTable.Rendering.Component
public void ClearTable()
{
m_Assembler?.DestroyAll();
- ClearPrefabInstances();
+ m_PrefabPool?.DestroyAll();
+ m_ActivePrefabMap?.Clear();
m_Quadtree?.Clear();
ClearAllChildren();
m_BackgroundGo = null;
@@ -1017,6 +1076,8 @@ namespace XericUI.XTable.Rendering.Component
ClearSelection();
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ m_PrefabPool = new PrefabPrototypePool(ContentTransform);
+ m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
MarkDirty(TableDirtyType.All);
RecalculateTotalSize();
CreateBackground();
@@ -1154,7 +1215,14 @@ namespace XericUI.XTable.Rendering.Component
// 预制体渲染
if (data.Prefab != null)
- PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
+ {
+ GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
+ RectTransform prt = instance.GetComponent();
+ prt.anchoredPosition = new Vector2(cellX, cellY);
+ prt.sizeDelta = new Vector2(cellW, cellH);
+ m_ActivePrefabMap[(r, c)] = instance;
+ data.OnPrefabAcquire?.Invoke(instance);
+ }
}
}
return;
@@ -1230,68 +1298,20 @@ namespace XericUI.XTable.Rendering.Component
// 预制体渲染
if (data.Prefab != null)
- PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
+ {
+ GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
+ RectTransform prt = instance.GetComponent();
+ prt.anchoredPosition = new Vector2(cellX, cellY);
+ prt.sizeDelta = new Vector2(cellW, cellH);
+ m_ActivePrefabMap[(r, c)] = instance;
+ data.OnPrefabAcquire?.Invoke(instance);
+ }
}
}
}
#endregion
- #region 预制体管理
-
- /// 获取或创建指定单元格的预制体实例
- 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;
- }
-
- /// 定位预制体实例到指定单元格区域(锚点左上角,枢轴左上角)
- 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();
- if (prt == null) prt = instance.AddComponent();
-
- 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);
- }
-
- /// 销毁所有已缓存的预制体实例
- 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)
@@ -1667,6 +1687,10 @@ namespace XericUI.XTable.Rendering.Component
m_ViewportRenderer.EndRow = m_FreezeRowCount;
m_ViewportRenderer.StartCol = 0;
m_ViewportRenderer.EndCol = m_FreezeColCount;
+
+ // 共享预制体对象池
+ m_ViewportRenderer.PrefabPool = m_PrefabPool;
+ m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap;
}
/// 渲染 Viewport 层冻结单元格,随滚动偏移调整位置
diff --git a/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
new file mode 100644
index 0000000..bcb61e1
--- /dev/null
+++ b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs
@@ -0,0 +1,186 @@
+using System.Collections.Generic;
+
+using UnityEngine;
+using UnityEngine.Pool;
+
+namespace XericUI.XTable.Rendering.Elements
+{
+ ///
+ /// 预制体对象池——按原型分组管理,每个预制体对应一个独立的对象池。
+ /// 使用 Stack + HashSet 模式与 保持一致。
+ ///
+ public class PrefabPrototypePool
+ {
+ #region 内部类型
+
+ private class PrefabPool
+ {
+ public GameObject Prototype;
+ /// 休眠实例栈(池满时先归还的优先复用)
+ public Stack Available = new Stack();
+ /// 活跃实例集合(O(1) 查找 + 防止重复归还)
+ public HashSet Active = new HashSet();
+ }
+
+ #endregion
+
+ #region 字段
+
+ private Dictionary m_Pools = new Dictionary();
+ private Transform m_Parent;
+ private Transform m_PoolRoot;
+
+ /// 池根节点(所有回收对象均挂在此节点下,节点本身处于 inactive)
+ public Transform PoolRoot => m_PoolRoot;
+
+ private const HideFlags POOL_FLAGS = HideFlags.DontSave | HideFlags.HideInHierarchy;
+
+ private static readonly Vector2 AnchorTopLeft = new Vector2(0, 1);
+ private static readonly Vector2 PivotTopLeft = new Vector2(0, 1);
+
+ #endregion
+
+ #region 构造 / 销毁
+
+ public PrefabPrototypePool(Transform parent)
+ {
+ m_Parent = parent;
+
+ GameObject poolRoot = new GameObject("_PrefabPoolRoot");
+ poolRoot.hideFlags = POOL_FLAGS;
+ poolRoot.transform.SetParent(parent, false);
+ poolRoot.SetActive(false);
+ m_PoolRoot = poolRoot.transform;
+ }
+
+ /// 彻底销毁所有池对象(活跃 + 休眠 + 池根)
+ public void DestroyAll()
+ {
+ foreach (var kv in m_Pools)
+ {
+ var pool = kv.Value;
+ // 销毁活跃实例
+ foreach (var go in pool.Active)
+ KillObject(go);
+ pool.Active.Clear();
+ // 销毁休眠实例
+ while (pool.Available.Count > 0)
+ KillObject(pool.Available.Pop());
+ }
+ m_Pools.Clear();
+
+ if (m_PoolRoot != null)
+ {
+ KillObject(m_PoolRoot.gameObject);
+ m_PoolRoot = null;
+ }
+ }
+
+ private static void KillObject(GameObject go)
+ {
+ if (go == null) return;
+ if (Application.isPlaying)
+ Object.Destroy(go);
+ else
+ Object.DestroyImmediate(go);
+ }
+
+ #endregion
+
+ #region 池操作
+
+ /// 从池中获取实例(池空则 Instantiate 新的)
+ public GameObject Acquire(GameObject prefab, int row, int col)
+ {
+ if (prefab == null) return null;
+
+ var pool = GetOrCreatePool(prefab);
+
+ GameObject instance;
+ if (pool.Available.Count > 0)
+ {
+ instance = pool.Available.Pop();
+ instance.SetActive(true);
+ instance.transform.SetParent(m_Parent, false);
+ }
+ else
+ {
+ instance = Object.Instantiate(prefab, m_Parent);
+ instance.hideFlags = POOL_FLAGS;
+ }
+
+ instance.name = $"_prefab_{row}_{col}";
+
+ // 强制 RectTransform 左上角坐标系
+ RectTransform rt = instance.GetComponent();
+ if (rt == null) rt = instance.AddComponent();
+ rt.anchorMin = AnchorTopLeft;
+ rt.anchorMax = AnchorTopLeft;
+ rt.pivot = PivotTopLeft;
+
+ pool.Active.Add(instance);
+ return instance;
+ }
+
+ /// 将指定实例归还对象池(Deactivate + 移入 PoolRoot)
+ public void Release(GameObject instance)
+ {
+ if (instance == null) return;
+
+ // 查找所属池
+ foreach (var kv in m_Pools)
+ {
+ if (kv.Value.Active.Remove(instance))
+ {
+ instance.SetActive(false);
+ instance.transform.SetParent(m_PoolRoot, false);
+ kv.Value.Available.Push(instance);
+ return;
+ }
+ }
+
+ // 不属于任何池(异常情况),直接销毁
+ KillObject(instance);
+ }
+
+ /// 将所有活跃实例回收至池
+ public void ReturnAll()
+ {
+ foreach (var kv in m_Pools)
+ {
+ var pool = kv.Value;
+ foreach (var go in pool.Active)
+ {
+ go.SetActive(false);
+ go.transform.SetParent(m_PoolRoot, false);
+ pool.Available.Push(go);
+ }
+ pool.Active.Clear();
+ }
+ }
+
+ /// 获取当前所有活跃实例(用于外部遍历回调)
+ public IReadOnlyCollection GetActive(GameObject prefab)
+ {
+ if (prefab == null || !m_Pools.TryGetValue(prefab, out var pool))
+ return System.Array.Empty();
+ return pool.Active;
+ }
+
+ #endregion
+
+ #region 内部方法
+
+ private PrefabPool GetOrCreatePool(GameObject prefab)
+ {
+ if (!m_Pools.TryGetValue(prefab, out var pool))
+ {
+ pool = new PrefabPool { Prototype = prefab };
+ m_Pools[prefab] = pool;
+ }
+ return pool;
+ }
+
+ #endregion
+ }
+}
diff --git a/Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs.meta
similarity index 83%
rename from Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta
rename to Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs.meta
index ce7f7a4..08d3865 100644
--- a/Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta
+++ b/Runtime/XTable/Rendering/Elements/PrefabPrototypePool.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 8ee1f2c601403504faea388e1f91f07b
+guid: 0bbffde7baff2d445b8b002b56b5c84d
MonoImporter:
externalObjects: {}
serializedVersion: 2