Merge remote-tracking branch 'gitea/thr_dev/dev2'
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Deconstruction.Runtime;
|
||||
#if ODIN_INSPECTOR
|
||||
using Sirenix.OdinInspector;
|
||||
#endif
|
||||
@@ -36,11 +37,9 @@ namespace XericUI.Core.Base
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_canvas == null)
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
if (!_canvas) _canvas = GetComponentInParent<Canvas>();
|
||||
#if UNITY_EDITOR
|
||||
if (_canvas == null)
|
||||
Debug.LogError($"{name} 无法找到布局组件,接下来的流程可能会引发错误", this);
|
||||
if (!_canvas) Debug.LogError($"{name} 无法找到布局组件,接下来的流程可能会引发错误", this);
|
||||
#endif
|
||||
return _canvas;
|
||||
}
|
||||
@@ -53,11 +52,9 @@ namespace XericUI.Core.Base
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_canvasScaler == null)
|
||||
_canvasScaler = GetComponentInParent<CanvasScaler>();
|
||||
if (!_canvasScaler) _canvasScaler = GetComponentInParent<CanvasScaler>();
|
||||
#if UNITY_EDITOR
|
||||
if (_canvas == null)
|
||||
Debug.LogError($"{name} 无法找到布局缩放组件,接下来的流程可能会引发错误", this);
|
||||
if (_canvas == null) Debug.LogError($"{name} 无法找到布局缩放组件,接下来的流程可能会引发错误", this);
|
||||
#endif
|
||||
return _canvasScaler;
|
||||
}
|
||||
@@ -70,11 +67,9 @@ namespace XericUI.Core.Base
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_graphicRaycaster == null)
|
||||
_graphicRaycaster = GetComponentInParent<GraphicRaycaster>();
|
||||
if (!_graphicRaycaster) _graphicRaycaster = GetComponentInParent<GraphicRaycaster>();
|
||||
#if UNITY_EDITOR
|
||||
if (_canvas == null)
|
||||
Debug.LogError($"{name} 无法找到布局射线检测,接下来的流程可能会引发错误", this);
|
||||
if (_canvas == null) Debug.LogError($"{name} 无法找到布局射线检测,接下来的流程可能会引发错误", this);
|
||||
#endif
|
||||
return _graphicRaycaster;
|
||||
}
|
||||
@@ -87,8 +82,7 @@ namespace XericUI.Core.Base
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rect == null)
|
||||
_rect = GetComponent<RectTransform>();
|
||||
if (!_rect) _rect = GetComponent<RectTransform>();
|
||||
return _rect;
|
||||
}
|
||||
}
|
||||
@@ -135,6 +129,7 @@ namespace XericUI.Core.Base
|
||||
/// </summary>
|
||||
public void SetHierarchyDirty()
|
||||
{
|
||||
rectTransform.SetLayoutDirty();
|
||||
_canvas = null;
|
||||
_canvasScaler = null;
|
||||
_graphicRaycaster = null;
|
||||
@@ -147,6 +142,29 @@ namespace XericUI.Core.Base
|
||||
public void ResetHierarchyDirty()
|
||||
{
|
||||
_hierarchyDirty = false;
|
||||
rectTransform.SetLayoutDirty();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 显影
|
||||
|
||||
public virtual void Show()
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Show(bool isShow)
|
||||
{
|
||||
if (isShow)
|
||||
Show();
|
||||
else
|
||||
Hide();
|
||||
}
|
||||
|
||||
public virtual void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace: default
|
||||
|
||||
/button/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
button/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.HighlightedColor = (0.96,0.96,0.96,1)
|
||||
.PressedColor = (0.78,0.78,0.78,1)
|
||||
@@ -12,7 +12,23 @@ namespace: default
|
||||
.DisabledColor = (0.6,0.6,0.6,0.8)
|
||||
}
|
||||
|
||||
/toggle/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
button/TargetGraphic/SpriteState:UnityEngine.UI.SpriteState/ {
|
||||
.NormalColor = guid::UnityEngine.Sprite
|
||||
.HighlightedColor = guid::UnityEngine.Sprite
|
||||
.PressedColor = guid::UnityEngine.Sprite
|
||||
.SelectedColor = guid::UnityEngine.Sprite
|
||||
.DisabledColor = guid::UnityEngine.Sprite
|
||||
}
|
||||
|
||||
button/TargetGraphic/Animation/ {
|
||||
.NormalColor = None
|
||||
.HighlightedColor = None
|
||||
.PressedColor = None
|
||||
.SelectedColor = None
|
||||
.DisabledColor = None
|
||||
}
|
||||
|
||||
toggle/TargetGraphic/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.HighlightedColor = (0.96,0.96,0.96,1)
|
||||
.PressedColor = (0.78,0.78,0.78,1)
|
||||
@@ -20,10 +36,24 @@ namespace: default
|
||||
.DisabledColor = (0.6,0.6,0.6,0.8)
|
||||
}
|
||||
|
||||
/image/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
toggle/TargetGraphic/SpriteState:UnityEngine.UI.SpriteState/ {
|
||||
.NormalColor = guid::UnityEngine.Sprite
|
||||
.HighlightedColor = guid::UnityEngine.Sprite
|
||||
.PressedColor = guid::UnityEngine.Sprite
|
||||
.SelectedColor = guid::UnityEngine.Sprite
|
||||
.DisabledColor = guid::UnityEngine.Sprite
|
||||
}
|
||||
|
||||
toggle/TargetGraphic/Animation/ {
|
||||
.NormalColor = None
|
||||
.HighlightedColor = None
|
||||
.PressedColor = None
|
||||
.SelectedColor = None
|
||||
.DisabledColor = None
|
||||
}
|
||||
|
||||
image/ColorTint:UnityEngine.UI.ColorBlock/ {
|
||||
.NormalColor = (1,1,1,1)
|
||||
.HighlightedColor = (0.96,0.96,0.96,1)
|
||||
.PressedColor = (0.78,0.78,0.78,1)
|
||||
.SelectedColor = (0.96,0.96,0.96,1)
|
||||
.DisabledColor = (0.6,0.6,0.6,0.8)
|
||||
.Sprite = guid::UnityEngine.Sprite
|
||||
.Material = guid::UnityEngine.Material
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
/// <summary>默认实例(五态默认匹配 ColorTint 颜色块)</summary>
|
||||
public static StyleSheetState Default => new StyleSheetState();
|
||||
|
||||
/// <summary>
|
||||
/// 创建纹理五态样式块,StylePath 默认匹配 SpriteState 块。
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ namespace XericUI.OverrideUI
|
||||
|
||||
[Header("样式表状态")] [SerializeField]
|
||||
[Tooltip("五态颜色样式块(ColorTint)")]
|
||||
private StyleSheetState m_ColorState = StyleSheetState.Default;
|
||||
private StyleSheetState m_ColorState = new StyleSheetState("button", Transition.ColorTint);
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("五态纹理样式块(SpriteState)")]
|
||||
private StyleSheetState m_TextureState = StyleSheetState.CreateTexture();
|
||||
private StyleSheetState m_TextureState = new StyleSheetState("button", Transition.SpriteSwap);
|
||||
|
||||
[SerializeField] [Tooltip("启用样式表驱动过渡(关闭则回退为 Unity ColorBlock)")]
|
||||
private bool m_EnableStyleTransition = true;
|
||||
|
||||
@@ -18,15 +18,15 @@ namespace XericUI.OverrideUI
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("颜色样式路径")]
|
||||
private StyleField m_ColorStyle = new StyleField();
|
||||
private StyleField m_ColorStyle = new StyleField("image/ColorTint/NormalColor");
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("纹理/精灵样式路径")]
|
||||
private StyleField m_TextureStyle = new StyleField();
|
||||
private StyleField m_TextureStyle = new StyleField("image/ColorTint/Sprite");
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("材质样式路径")]
|
||||
private StyleField m_MaterialStyle = new StyleField();
|
||||
private StyleField m_MaterialStyle = new StyleField("image/ColorTint/Material");
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("启用样式表自动刷新")]
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace XericUI.OverrideUI
|
||||
[Header("样式表状态")]
|
||||
[SerializeField]
|
||||
[Tooltip("五态颜色样式块(ColorTint)")]
|
||||
private StyleSheetState m_ColorState = StyleSheetState.Default;
|
||||
private StyleSheetState m_ColorState = new StyleSheetState("toggle", Transition.ColorTint);
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("五态纹理样式块(SpriteState)")]
|
||||
private StyleSheetState m_TextureState = StyleSheetState.CreateTexture();
|
||||
private StyleSheetState m_TextureState = new StyleSheetState("toggle", Transition.SpriteSwap);
|
||||
|
||||
[Header("Toggle 专用")]
|
||||
[SerializeField]
|
||||
|
||||
@@ -1,48 +1,53 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 单元格数据模型——仅保存数据内容,不保存自身尺寸。
|
||||
/// 尺寸由渲染阶段的行列标题(行高/列宽)决定。
|
||||
/// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class XTableCellData
|
||||
{
|
||||
/// <summary>文本内容</summary>
|
||||
public string Text;
|
||||
/// <summary>
|
||||
/// 单元格数据模型——仅保存数据内容,不保存自身尺寸。
|
||||
/// 尺寸由渲染阶段的行列标题(行高/列宽)决定。
|
||||
/// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class XTableCellData
|
||||
{
|
||||
/// <summary>文本内容</summary>
|
||||
public string Text;
|
||||
|
||||
/// <summary>图片纹理</summary>
|
||||
public Texture2D Image;
|
||||
/// <summary>图片纹理</summary>
|
||||
public Texture2D Image;
|
||||
|
||||
/// <summary>预制体对象(用于嵌入复杂 UI 元素)</summary>
|
||||
public GameObject Prefab;
|
||||
/// <summary>预制体对象(用于嵌入复杂 UI 元素)</summary>
|
||||
public GameObject Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// 样式命名空间——对应 StyleManager 中的命名空间。
|
||||
/// 为空或 "xeric_table_default" 时使用默认表格样式。
|
||||
/// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。
|
||||
/// </summary>
|
||||
public string StyleNamespace;
|
||||
/// <summary>
|
||||
/// 样式命名空间——对应 StyleManager 中的命名空间。
|
||||
/// 为空或 "xeric_table_default" 时使用默认表格样式。
|
||||
/// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。
|
||||
/// </summary>
|
||||
public string StyleNamespace;
|
||||
|
||||
public XTableCellData() { }
|
||||
/// <summary>预制体实例从对象池取出时回调——外部可在此初始化实例内容</summary>
|
||||
[NonSerialized] public System.Action<GameObject> OnPrefabAcquire;
|
||||
|
||||
public XTableCellData(string text)
|
||||
{
|
||||
Text = text;
|
||||
}
|
||||
/// <summary>预制体实例归还对象池前回调——外部可在此清理引用</summary>
|
||||
[NonSerialized] public System.Action<GameObject> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericUI.XTable.Core;
|
||||
using XericUI.XTable.Rendering.Elements;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格渲染管线封装——将 CellAssembler 对象池、背景创建、单元格渲染抽离为可复用单元。
|
||||
/// 同一表格可持有多个 TableRenderer 实例,分别渲染到不同父节点(如 Content 和 Viewport)。
|
||||
/// </summary>
|
||||
public class TableRenderer
|
||||
{
|
||||
#region 内部字段
|
||||
|
||||
private Transform m_Parent;
|
||||
private CellAssembler m_Assembler;
|
||||
private GameObject m_BackgroundGo;
|
||||
|
||||
/// <summary>共享预制体对象池引用(由 XericUIActionTable 传入)</summary>
|
||||
public PrefabPrototypePool PrefabPool;
|
||||
|
||||
/// <summary>共享活跃预制体映射表(由 XericUIActionTable 传入)</summary>
|
||||
public Dictionary<(int, int), GameObject> ActivePrefabMap;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公开属性
|
||||
|
||||
public CellAssembler Assembler => m_Assembler;
|
||||
public GameObject BackgroundGo => m_BackgroundGo;
|
||||
public Transform Parent => m_Parent;
|
||||
|
||||
/// <summary>起始行偏移(Viewport 渲染器通常为 0;Content 渲染器通常为 freezeRowCount)</summary>
|
||||
public int StartRow;
|
||||
/// <summary>起始列偏移</summary>
|
||||
public int StartCol;
|
||||
/// <summary>终止行(不含),0 表示不受限</summary>
|
||||
public int EndRow;
|
||||
/// <summary>终止列(不含),0 表示不受限</summary>
|
||||
public int EndCol;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 样式委托
|
||||
|
||||
/// <summary>从样式表读取颜色值</summary>
|
||||
public System.Func<string, string, Color, Color> GetStyleColor;
|
||||
/// <summary>从样式表读取浮点值</summary>
|
||||
public System.Func<string, string, float, float> GetStyleFloat;
|
||||
/// <summary>从样式表读取整数值</summary>
|
||||
public System.Func<string, string, int, int> GetStyleInt;
|
||||
/// <summary>从样式表读取 TMP_FontAsset</summary>
|
||||
public System.Func<string, string, TMP_FontAsset> GetStyleFontAsset;
|
||||
|
||||
/// <summary>获取行高数组</summary>
|
||||
public System.Func<float[]> GetRowHeights;
|
||||
/// <summary>获取列宽数组</summary>
|
||||
public System.Func<float[]> GetColWidths;
|
||||
/// <summary>获取表格数据</summary>
|
||||
public System.Func<XTableData> GetTableData;
|
||||
/// <summary>默认样式命名空间</summary>
|
||||
public System.Func<string> GetDefaultStyleNamespace;
|
||||
/// <summary>判断单元格是否在多选范围内</summary>
|
||||
public System.Func<int, int, bool> IsInSelectionRange;
|
||||
/// <summary>获取选中的起始行列</summary>
|
||||
public System.Func<int> GetSelectedRow;
|
||||
/// <summary>获取选中的起始列</summary>
|
||||
public System.Func<int> GetSelectedCol;
|
||||
|
||||
/// <summary>运行时样式变更回调</summary>
|
||||
public event System.Action OnStyleChangeDetected;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造 / 销毁
|
||||
|
||||
public TableRenderer(Transform parent, string defaultStyleNamespace = null,
|
||||
System.Action styleChangeHandler = null)
|
||||
{
|
||||
m_Parent = parent;
|
||||
m_Assembler = new CellAssembler(parent, defaultStyleNamespace);
|
||||
if (styleChangeHandler != null)
|
||||
m_Assembler.OnStyleChangeDetected += styleChangeHandler;
|
||||
}
|
||||
|
||||
/// <summary>彻底销毁渲染器中所有动态生成的对象</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 背景
|
||||
|
||||
/// <summary>创建或获取背景 Image(锚定左上角,覆盖整个渲染范围)</summary>
|
||||
public void CreateBackground(float totalWidth, float totalHeight, string styleNamespace)
|
||||
{
|
||||
if (m_BackgroundGo != null)
|
||||
{
|
||||
var rt = m_BackgroundGo.GetComponent<RectTransform>();
|
||||
rt.sizeDelta = new Vector2(totalWidth, totalHeight);
|
||||
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();
|
||||
|
||||
RectTransform bgRt = m_BackgroundGo.GetComponent<RectTransform>();
|
||||
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<Image>();
|
||||
bgImg.color = GetStyleColor?.Invoke(styleNamespace,
|
||||
XericUIActionTable.STYLE_CELL_BG_COLOR,
|
||||
XericUIActionTable.BG_COLOR_FALLBACK)
|
||||
?? XericUIActionTable.BG_COLOR_FALLBACK;
|
||||
bgImg.raycastTarget = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 单元格渲染
|
||||
|
||||
/// <summary>回收所有池对象</summary>
|
||||
public void ReturnAll() => m_Assembler.ReturnAll();
|
||||
|
||||
/// <summary>
|
||||
/// 渲染指定范围内的单元格。
|
||||
/// 行范围为 [sr, er),列范围为 [sc, ec)。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderCellsRuntime(sr, er, sc, ec, rowHeights, colWidths, tableData, defaultNs);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCellsEditor(int sr, int er, int sc, int ec,
|
||||
float[] rowHeights, float[] colWidths, XTableData tableData, string defaultNs)
|
||||
{
|
||||
Color selectionColor = GetStyleColor(defaultNs, XericUIActionTable.STYLE_CELL_SELECTION_BG_COLOR,
|
||||
XericUIActionTable.SELECTION_COLOR_FALLBACK);
|
||||
Color borderColor = GetStyleColor(defaultNs, XericUIActionTable.STYLE_CELL_BORDER_BOTTOM_COLOR,
|
||||
XericUIActionTable.BORDER_COLOR_FALLBACK);
|
||||
float borderWidth = GetStyleFloat(defaultNs, XericUIActionTable.STYLE_CELL_BORDER_BOTTOM_WIDTH,
|
||||
XericUIActionTable.BORDER_WIDTH_FALLBACK);
|
||||
|
||||
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);
|
||||
|
||||
int selRow = GetSelectedRow();
|
||||
int selCol = GetSelectedCol();
|
||||
|
||||
for (int r = sr; r < er && r < rowHeights.Length; r++)
|
||||
{
|
||||
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 && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : defaultNs;
|
||||
|
||||
// 选择高亮
|
||||
if (IsInSelectionRange != null && IsInSelectionRange(r, c))
|
||||
{
|
||||
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 == selRow && c == selCol) ? focusColor : multiSelectColor;
|
||||
}
|
||||
else if (r == selRow && c == selCol)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
// 网格线
|
||||
var hLine = m_Assembler.GetImage("cell_hline");
|
||||
hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
|
||||
hLine.Rect.sizeDelta = new Vector2(cellW, borderWidth);
|
||||
hLine.Image.color = borderColor;
|
||||
|
||||
var vLine = m_Assembler.GetImage("cell_vline");
|
||||
vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - borderWidth, cellY);
|
||||
vLine.Rect.sizeDelta = new Vector2(borderWidth, cellH);
|
||||
vLine.Image.color = borderColor;
|
||||
|
||||
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 = 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;
|
||||
}
|
||||
|
||||
if (data.Prefab != null && PrefabPool != null)
|
||||
{
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
if (ActivePrefabMap != null)
|
||||
ActivePrefabMap[(r, c)] = instance;
|
||||
data.OnPrefabAcquire?.Invoke(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCellsRuntime(int sr, int er, int sc, int ec,
|
||||
float[] rowHeights, float[] colWidths, XTableData tableData, string defaultNs)
|
||||
{
|
||||
Color rtSelectionColor = XericUIActionTable.RT_SELECTION_COLOR;
|
||||
Color rtMultiSelectColor = XericUIActionTable.RT_MULTI_SELECT_COLOR;
|
||||
Color rtFocusColor = XericUIActionTable.RT_FOCUS_COLOR;
|
||||
|
||||
int selRow = GetSelectedRow();
|
||||
int selCol = GetSelectedCol();
|
||||
|
||||
for (int r = sr; r < er && r < rowHeights.Length; r++)
|
||||
{
|
||||
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 && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : defaultNs;
|
||||
|
||||
if (IsInSelectionRange != null && IsInSelectionRange(r, c))
|
||||
{
|
||||
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 == selRow && c == selCol) ? rtFocusColor : rtMultiSelectColor;
|
||||
}
|
||||
else if (r == selRow && c == selCol)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
var hLine = m_Assembler.GetImage("cell_hline");
|
||||
hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
|
||||
hLine.Rect.sizeDelta = new Vector2(cellW, XericUIActionTable.RT_BORDER_WIDTH);
|
||||
hLine.Image.color = XericUIActionTable.RT_BORDER_COLOR;
|
||||
|
||||
var vLine = m_Assembler.GetImage("cell_vline");
|
||||
vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - XericUIActionTable.RT_BORDER_WIDTH, cellY);
|
||||
vLine.Rect.sizeDelta = new Vector2(XericUIActionTable.RT_BORDER_WIDTH, cellH);
|
||||
vLine.Image.color = XericUIActionTable.RT_BORDER_COLOR;
|
||||
|
||||
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 && PrefabPool != null)
|
||||
{
|
||||
GameObject instance = PrefabPool.Acquire(data.Prefab, r, c);
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
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 float CalcRowTopY(int row, float[] rowHeights)
|
||||
{
|
||||
float y = 0;
|
||||
for (int i = 0; i < row && i < rowHeights.Length; i++)
|
||||
y += rowHeights[i];
|
||||
return y;
|
||||
}
|
||||
|
||||
private float CalcColLeftX(int col, float[] colWidths)
|
||||
{
|
||||
float x = 0;
|
||||
for (int i = 0; i < col && i < colWidths.Length; i++)
|
||||
x += colWidths[i];
|
||||
return x;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de1e4b2ad4f55bc48a57a820276024b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,12 +63,18 @@ 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;
|
||||
|
||||
/// <summary>预制体实例缓存,key 为 (row, col),用于复用已实例化的预制体</summary>
|
||||
[System.NonSerialized] private Dictionary<(int, int), GameObject> m_PrefabInstances;
|
||||
/// <summary>预制体对象池——按原型分组管理</summary>
|
||||
[System.NonSerialized] private PrefabPrototypePool m_PrefabPool;
|
||||
|
||||
/// <summary>活跃预制体实例映射 (row, col) → instance,用于回调编排</summary>
|
||||
[System.NonSerialized] private Dictionary<(int, int), GameObject> m_ActivePrefabMap;
|
||||
|
||||
/// <summary>表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限</summary>
|
||||
[System.NonSerialized] private XTableQuadtree m_Quadtree;
|
||||
@@ -105,6 +119,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>
|
||||
@@ -234,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<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
m_ActivePrefabMap[(row, col)] = instance;
|
||||
data.OnPrefabAcquire?.Invoke(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,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<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
m_ActivePrefabMap[(row, col)] = instance;
|
||||
data.OnPrefabAcquire?.Invoke(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -327,11 +370,16 @@ 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();
|
||||
CreateBackground();
|
||||
|
||||
// 创建 Viewport 层渲染器(用于冻结单元格)
|
||||
CreateViewportRenderer();
|
||||
|
||||
if (m_ScrollRect != null)
|
||||
m_ScrollRect.onValueChanged.AddListener(OnScrollValueChanged);
|
||||
|
||||
@@ -376,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)
|
||||
{
|
||||
@@ -385,6 +434,10 @@ namespace XericUI.XTable.Rendering.Component
|
||||
m_BackgroundGo = null;
|
||||
}
|
||||
|
||||
// 销毁 Viewport 冻结渲染器
|
||||
m_ViewportRenderer?.DestroyAll();
|
||||
m_ViewportRenderer = null;
|
||||
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
@@ -464,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);
|
||||
|
||||
/// <summary>为指定单元格设置预制体</summary>
|
||||
public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
|
||||
{
|
||||
var data = TableData.GetOrCreateCell(row, col);
|
||||
data.Prefab = prefab;
|
||||
MarkDirty(TableDirtyType.DataChanged);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>清除指定单元格的预制体</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>设置单元格的样式命名空间,同时写入边框缓存(谁最后设置归谁)</summary>
|
||||
public void SetCellStyleNamespace(int row, int col, string styleNamespace)
|
||||
@@ -799,19 +873,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;
|
||||
@@ -840,6 +927,9 @@ namespace XericUI.XTable.Rendering.Component
|
||||
// 重建边框线段
|
||||
RebuildBorders();
|
||||
|
||||
// 渲染 Viewport 层冻结单元格
|
||||
RenderFrozenCells();
|
||||
|
||||
m_DirtyFlag.Clear();
|
||||
OnTableRefreshed?.Invoke();
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
@@ -919,6 +1009,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
public void FullRebuild()
|
||||
{
|
||||
m_Assembler?.DestroyAll();
|
||||
m_PrefabPool?.DestroyAll();
|
||||
ClearAllChildren();
|
||||
m_BackgroundGo = null;
|
||||
m_BorderRenderer = null;
|
||||
@@ -926,9 +1017,12 @@ 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();
|
||||
CreateViewportRenderer();
|
||||
MarkDirty(TableDirtyType.All);
|
||||
RefreshView();
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
@@ -979,7 +1073,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);
|
||||
@@ -992,7 +1087,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;
|
||||
@@ -1004,9 +1100,12 @@ 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();
|
||||
CreateViewportRenderer();
|
||||
RefreshView();
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
}
|
||||
@@ -1140,7 +1239,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<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
m_ActivePrefabMap[(r, c)] = instance;
|
||||
data.OnPrefabAcquire?.Invoke(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -1216,68 +1322,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<RectTransform>();
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
m_ActivePrefabMap[(r, c)] = instance;
|
||||
data.OnPrefabAcquire?.Invoke(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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)
|
||||
@@ -1655,6 +1713,109 @@ 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;
|
||||
|
||||
// 共享预制体对象池
|
||||
m_ViewportRenderer.PrefabPool = m_PrefabPool;
|
||||
m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap;
|
||||
}
|
||||
|
||||
/// <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)
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
/// <summary>
|
||||
/// 预制体对象池——按原型分组管理,每个预制体对应一个独立的对象池。
|
||||
/// 使用 Stack + HashSet 模式与 <see cref="CellAssembler"/> 保持一致。
|
||||
/// </summary>
|
||||
public class PrefabPrototypePool
|
||||
{
|
||||
#region 内部类型
|
||||
|
||||
private class PrefabPool
|
||||
{
|
||||
public GameObject Prototype;
|
||||
/// <summary>休眠实例栈(池满时先归还的优先复用)</summary>
|
||||
public Stack<GameObject> Available = new Stack<GameObject>();
|
||||
/// <summary>活跃实例集合(O(1) 查找 + 防止重复归还)</summary>
|
||||
public HashSet<GameObject> Active = new HashSet<GameObject>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字段
|
||||
|
||||
private Dictionary<GameObject, PrefabPool> m_Pools = new Dictionary<GameObject, PrefabPool>();
|
||||
private Transform m_Parent;
|
||||
private Transform m_PoolRoot;
|
||||
|
||||
/// <summary>池根节点(所有回收对象均挂在此节点下,节点本身处于 inactive)</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>彻底销毁所有池对象(活跃 + 休眠 + 池根)</summary>
|
||||
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 池操作
|
||||
|
||||
/// <summary>从池中获取实例(池空则 Instantiate 新的)</summary>
|
||||
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<RectTransform>();
|
||||
if (rt == null) rt = instance.AddComponent<RectTransform>();
|
||||
rt.anchorMin = AnchorTopLeft;
|
||||
rt.anchorMax = AnchorTopLeft;
|
||||
rt.pivot = PivotTopLeft;
|
||||
|
||||
pool.Active.Add(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>将指定实例归还对象池(Deactivate + 移入 PoolRoot)</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>将所有活跃实例回收至池</summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>获取当前所有活跃实例(用于外部遍历回调)</summary>
|
||||
public IReadOnlyCollection<GameObject> GetActive(GameObject prefab)
|
||||
{
|
||||
if (prefab == null || !m_Pools.TryGetValue(prefab, out var pool))
|
||||
return System.Array.Empty<GameObject>();
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bbffde7baff2d445b8b002b56b5c84d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user