Files
XericUIActionVessel/Runtime/XTable/Rendering/Elements/CellAssembler.cs
T

302 lines
7.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace XericUI.XTable.Rendering.Elements
{
/// <summary>
/// 表格单元动态拼装器——通过对象池管理 Image + TMP_Text 的组合创建与回收。
/// 所有元素统一使用 anchor=(0,1) pivot=(0,1) 左上角坐标系。
/// 所有动态生成对象标记 HideFlags.DontSave | HideFlags.HideInHierarchy,防止泄漏到场景。
/// </summary>
public class CellAssembler
{
#region 对象池定义
public class PooledImage
{
public GameObject Root;
public RectTransform Rect;
public Image Image;
public StyleBoundElement Style;
}
public class PooledText
{
public GameObject Root;
public RectTransform Rect;
public TMP_Text Text;
public StyleBoundElement Style;
}
#endregion
#region 字段
private Transform m_Parent;
private Transform m_PoolRoot;
/// <summary>池根节点(所有回收对象均挂在此节点下,节点本身处于 inactive)</summary>
public Transform PoolRoot => m_PoolRoot;
/// <summary>运行时的默认样式命名空间(编辑器下为 null,不绑定)</summary>
private string m_DefaultStyleNamespace;
/// <summary>所有已绑定的 StyleBoundElement 回调集合(用于样式变更时通知外部)</summary>
public event System.Action OnStyleChangeDetected;
private Stack<PooledImage> m_ImagePool = new Stack<PooledImage>();
private Stack<PooledText> m_TextPool = new Stack<PooledText>();
private List<PooledImage> m_ActiveImages = new List<PooledImage>();
private List<PooledText> m_ActiveTexts = new List<PooledText>();
/// <summary>HideFlags 应用于所有动态创建的对象(不保存、不在 Hierarchy 显示)</summary>
private const HideFlags POOL_FLAGS = HideFlags.DontSave | HideFlags.HideInHierarchy;
#endregion
#region 构造函数与析构
public CellAssembler(Transform parent, string defaultStyleNamespace = null)
{
m_Parent = parent;
m_DefaultStyleNamespace = defaultStyleNamespace;
GameObject poolRoot = new GameObject("_CellPool");
poolRoot.hideFlags = POOL_FLAGS;
poolRoot.transform.SetParent(parent, false);
poolRoot.SetActive(false);
m_PoolRoot = poolRoot.transform;
}
/// <summary>彻底销毁所有池对象(包括活跃和休眠的)</summary>
public void DestroyAll()
{
// 杀死活跃对象
for (int i = m_ActiveImages.Count - 1; i >= 0; i--)
{
KillObject(m_ActiveImages[i].Root);
}
m_ActiveImages.Clear();
for (int i = m_ActiveTexts.Count - 1; i >= 0; i--)
{
KillObject(m_ActiveTexts[i].Root);
}
m_ActiveTexts.Clear();
// 杀死池中对象
while (m_ImagePool.Count > 0)
{
KillObject(m_ImagePool.Pop().Root);
}
while (m_TextPool.Count > 0)
{
KillObject(m_TextPool.Pop().Root);
}
// 杀死池根
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 常量
private static readonly Vector2 ANCHOR_TOPLEFT_MIN = new Vector2(0, 1);
private static readonly Vector2 ANCHOR_TOPLEFT_MAX = new Vector2(0, 1);
private static readonly Vector2 PIVOT_TOPLEFT = new Vector2(0, 1);
#endregion
#region 池操作
public PooledImage GetImage(string name = "cell_img", string styleNamespace = null)
{
PooledImage item;
if (m_ImagePool.Count > 0)
{
item = m_ImagePool.Pop();
item.Root.SetActive(true);
item.Root.transform.SetParent(m_Parent, false);
ResetImageDefaults(item);
}
else
{
item = CreateImage(name);
}
BindStyle(item.Style, styleNamespace);
m_ActiveImages.Add(item);
return item;
}
public PooledText GetText(string name = "cell_text", string styleNamespace = null)
{
PooledText item;
if (m_TextPool.Count > 0)
{
item = m_TextPool.Pop();
item.Root.SetActive(true);
item.Root.transform.SetParent(m_Parent, false);
ResetTextDefaults(item);
}
else
{
item = CreateText(name);
}
BindStyle(item.Style, styleNamespace);
m_ActiveTexts.Add(item);
return item;
}
/// <summary>运行时绑定样式到池元素的 StyleBoundElement,实现被动更新(push)</summary>
private void BindStyle(StyleBoundElement style, string cellNamespace)
{
if (!Application.isPlaying) return;
string ns = cellNamespace ?? m_DefaultStyleNamespace;
if (string.IsNullOrEmpty(ns)) return;
if (style != null && style.BoundNamespace != ns)
{
style.OnStyleChangedCallback -= OnStyleChangedNotify;
style.OnStyleChangedCallback += OnStyleChangedNotify;
style.Bind(ns);
}
}
private void OnStyleChangedNotify()
{
OnStyleChangeDetected?.Invoke();
}
public void ReturnAll()
{
foreach (var img in m_ActiveImages) ReturnImage(img);
m_ActiveImages.Clear();
foreach (var txt in m_ActiveTexts) ReturnText(txt);
m_ActiveTexts.Clear();
}
private void ReturnImage(PooledImage item)
{
item.Style?.Unbind();
item.Root.SetActive(false);
item.Root.transform.SetParent(m_PoolRoot, false);
m_ImagePool.Push(item);
}
private void ReturnText(PooledText item)
{
item.Style?.Unbind();
item.Text.text = "";
item.Root.SetActive(false);
item.Root.transform.SetParent(m_PoolRoot, false);
m_TextPool.Push(item);
}
#endregion
#region 默认值重置
private void ResetImageDefaults(PooledImage item)
{
item.Rect.anchorMin = ANCHOR_TOPLEFT_MIN;
item.Rect.anchorMax = ANCHOR_TOPLEFT_MAX;
item.Rect.pivot = PIVOT_TOPLEFT;
item.Rect.anchoredPosition = Vector2.zero;
item.Rect.sizeDelta = Vector2.zero;
item.Image.color = Color.white;
item.Image.sprite = null;
item.Image.raycastTarget = false;
}
private void ResetTextDefaults(PooledText item)
{
item.Rect.anchorMin = ANCHOR_TOPLEFT_MIN;
item.Rect.anchorMax = ANCHOR_TOPLEFT_MAX;
item.Rect.pivot = PIVOT_TOPLEFT;
item.Rect.anchoredPosition = Vector2.zero;
item.Rect.sizeDelta = Vector2.zero;
item.Text.text = "";
item.Text.color = Color.black;
item.Text.fontSize = 14;
item.Text.alignment = TextAlignmentOptions.Midline;
item.Text.raycastTarget = false;
}
#endregion
#region 创建
private PooledImage CreateImage(string name)
{
GameObject go = new GameObject(name,
typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
go.hideFlags = POOL_FLAGS;
go.transform.SetParent(m_Parent, false);
Image img = go.GetComponent<Image>();
img.raycastTarget = false;
RectTransform rect = go.GetComponent<RectTransform>();
rect.anchorMin = ANCHOR_TOPLEFT_MIN;
rect.anchorMax = ANCHOR_TOPLEFT_MAX;
rect.pivot = PIVOT_TOPLEFT;
return new PooledImage
{
Root = go,
Rect = rect,
Image = img,
Style = new StyleBoundElement { ImageComponent = img }
};
}
private PooledText CreateText(string name)
{
GameObject go = new GameObject(name,
typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
go.hideFlags = POOL_FLAGS;
go.transform.SetParent(m_Parent, false);
TMP_Text text = go.GetComponent<TMP_Text>();
text.raycastTarget = false;
text.alignment = TextAlignmentOptions.Midline;
text.fontSize = 14;
RectTransform rect = go.GetComponent<RectTransform>();
rect.anchorMin = ANCHOR_TOPLEFT_MIN;
rect.anchorMax = ANCHOR_TOPLEFT_MAX;
rect.pivot = PIVOT_TOPLEFT;
return new PooledText
{
Root = go,
Rect = rect,
Text = text,
Style = new StyleBoundElement { TextComponent = text }
};
}
#endregion
}
}