优化反射绑定

This commit is contained in:
2026-07-24 14:51:03 +08:00
parent e63f1fb000
commit 39220c761f
9 changed files with 195 additions and 122 deletions
+26 -17
View File
@@ -135,13 +135,13 @@ namespace XericUI.Core.Base
_graphicRaycaster = null;
_hierarchyDirty = true;
}
/// <summary>
/// 复位层级脏
/// </summary>
public void ResetHierarchyDirty()
{
_hierarchyDirty = false;
_hierarchyDirty = false;
rectTransform.SetLayoutDirty();
}
@@ -241,21 +241,30 @@ namespace XericUI.Core.Base
{
var rt = rectTransform;
if (rt == null || target == null) return;
var parentRT = rt.parent as RectTransform;
if (parentRT == null) return;
Vector3[] corners = new Vector3[4];
target.GetWorldCorners(corners);
// corners[0]=左下, corners[1]=左上, corners[2]=右上, corners[3]=右下
Vector3 worldPos = Vector3.Lerp(
Vector3.Lerp(corners[0], corners[1], anchor.y),
Vector3.Lerp(corners[3], corners[2], anchor.y),
anchor.x);
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
parentRT, worldPos, null, out Vector2 localPoint))
if (rt.parent == target.parent)
{
rt.anchoredPosition = localPoint;
rt.anchorMin = anchor;
rt.anchorMax = anchor;
rt.pivot = anchor;
rt.anchoredPosition = Vector2.zero;
return;
}
var targetWorldPos = target.TransformPoint(new Vector3(anchor.x, anchor.y, 0));
// RectTransformUtility 会自动处理父级不一致的情况
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
rt.parent as RectTransform,
targetWorldPos,
null,
out Vector2 anchorLocalPos))
{
Vector2 pivotOffset = new Vector2(
(rt.pivot.x - 0.5f) * rt.rect.width,
(rt.pivot.y - 0.5f) * rt.rect.height
);
rt.anchoredPosition = anchorLocalPos + pivotOffset;
}
}
@@ -265,9 +274,9 @@ namespace XericUI.Core.Base
public virtual void Show()
{
gameObject.SetActive(true);
gameObject.SetActive(true);
}
public void Show(bool isShow)
{
if (isShow)
@@ -4,12 +4,21 @@ namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 字段编组特性
/// 将多个字段标记为属于同一个 Entry,共享一个 UI 组件实例
/// 将多个字段标记为属于同一个编组路径,共享一个 UI 组件实例
///
/// 编组不携带 Tag,Tag 由 [XuiavrgFieldTag] 单独标记。
/// 使用示例:
/// [XuiavrgFieldEntryGroup("field1", XuiavrgFieldTag.Label)] public string fieldLabel = "速度";
/// [XuiavrgFieldEntryGroup("field1", XuiavrgFieldTag.Context)] public float fieldValue = 0.1f;
/// [XuiavrgFieldEntryGroup("field1", XuiavrgFieldTag.Unit)] public string fieldUnit = "m/s";
/// [XuiavrgFieldEntryGroup("field1")]
/// [XuiavrgFieldTag(XuiavrgFieldTag.Label)]
/// public string fieldLabel = "速度";
///
/// [XuiavrgFieldEntryGroup("field1")]
/// [XuiavrgFieldTag(XuiavrgFieldTag.Context)]
/// public float fieldValue = 0.1f;
///
/// [XuiavrgFieldEntryGroup("field1")]
/// [XuiavrgFieldTag(XuiavrgFieldTag.Unit)]
/// public string fieldUnit = "m/s";
///
/// 上述三个字段将合并为一个 Entry,分别绑定到 UI 组件上 Label、Context、Unit 三个子元素
/// </summary>
@@ -18,31 +27,20 @@ namespace XericUI.ReflectionUIGenerator
{
/// <summary>编组路径,以 "/" 分割层级。同一 groupPath 的字段合并到一个 Entry</summary>
public readonly string GroupPath;
/// <summary>该字段在编组 Entry 中的功能标记</summary>
public readonly XuiavrgFieldTag Tag;
/// <summary>同一 Tag 存在多个时的索引区分</summary>
public readonly int TagIndex;
/// <summary>
/// 创建字段编组标记
/// </summary>
/// <param name="groupPath">编组路径 (如 "field1" 或 "field1/child")</param>
/// <param name="tag">功能标记</param>
/// <param name="tagIndex">标记索引 (同一 Tag 存在多个时区分)</param>
public XuiavrgFieldEntryGroupAttribute(string groupPath, XuiavrgFieldTag tag, int tagIndex = 0)
/// <param name="groupPath">编组路径 (如 "field1" 或 "Stats/Speed")</param>
public XuiavrgFieldEntryGroupAttribute(string groupPath)
{
GroupPath = groupPath;
Tag = tag;
TagIndex = tagIndex;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
metaData.GroupPath = GroupPath;
metaData.Tag = Tag;
metaData.TagIndex = TagIndex;
// 不设置 Tag/TagIndex,留给 [XuiavrgFieldTag] 单独处理
}
}
}
@@ -12,7 +12,7 @@ namespace XericUI.ReflectionUIGenerator
/// Manager 在 GenerateUI 阶段会优先使用此名称查找 PropertyGroup
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class XuiavrgFieldPrefabOverrideAttribute : Attribute, IXuiavrgAttribute
public class XuiavrgFieldNameAttribute : Attribute, IXuiavrgAttribute
{
/// <summary>覆盖的预制体组名称</summary>
public readonly string PrefabGroupName;
@@ -23,7 +23,7 @@ namespace XericUI.ReflectionUIGenerator
/// <param name="prefabGroupName">
/// 目标预制体名称,在 AvrgTypePrefabMappingConfig 中查找匹配
/// </param>
public XuiavrgFieldPrefabOverrideAttribute(string prefabGroupName)
public XuiavrgFieldNameAttribute(string prefabGroupName)
{
PrefabGroupName = prefabGroupName;
}
@@ -0,0 +1,44 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 字段功能标记特性
/// 单独设置字段的功能标记,与编组分离
///
/// 使用示例:
/// [XuiavrgFieldEntryGroup("field1")]
/// [XuiavrgFieldTag(XuiavrgFieldTag.Label)]
/// public string fieldLabel = "速度";
///
/// [XuiavrgFieldEntryGroup("field1")]
/// [XuiavrgFieldTag(XuiavrgFieldTag.Context)]
/// public float fieldValue = 0.1f;
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class XuiavrgFieldTagAttribute : Attribute, IXuiavrgAttribute
{
/// <summary>功能标记</summary>
public readonly XuiavrgFieldTag Tag;
/// <summary>同一 Tag 存在多个时的索引区分</summary>
public readonly int TagIndex;
/// <summary>
/// 创建字段功能标记
/// </summary>
/// <param name="tag">功能标记</param>
/// <param name="tagIndex">标记索引(同一 Tag 存在多个时区分)</param>
public XuiavrgFieldTagAttribute(XuiavrgFieldTag tag, int tagIndex = 0)
{
Tag = tag;
TagIndex = tagIndex;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
metaData.Tag = Tag;
metaData.TagIndex = TagIndex;
}
}
}
@@ -85,23 +85,6 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>是否启用无效值状态检查</summary>
public bool ValidateInvalid { get; set; }
// ============ (已弃用字段保留) 旧的验证约束 ============
/// <summary>字符串最小长度</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public int StringMinLength { get; set; }
/// <summary>字符串最大长度</summary>
public int StringMaxLength { get; set; } = int.MaxValue;
/// <summary>数值最小值</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public float NumberMinValue { get; set; }
/// <summary>数值最大值</summary>
[Obsolete("将移至 ValueRangeConfig 管理")]
public float NumberMaxValue { get; set; }
// ============ 原始 Attribute 引用 ============
/// <summary>
@@ -7,8 +7,8 @@ using Object = UnityEngine.Object;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 节点下生成反射属性管理器 — Metadata-Driven View Model (MDVM 元数据驱动的视图数据模型)
/// 负责:构建 Entry → 创建组容器 → 生成 UI → 绑定数据
/// 节点下生成反射属性管理器 — MVM (Metadata-Driven View Model)
/// 负责:构建 Entry → 创建组容器(树状嵌套)→ 生成 UI → 绑定数据
/// </summary>
public class XuiavrgInspectorManager : MonoBehaviour
{
@@ -75,9 +75,9 @@ namespace XericUI.ReflectionUIGenerator
public AvrgMemberMetaDataSet CurrentMetaDataSet { get; private set; }
/// <summary>
/// 当前活跃的组容器字典
/// 编组叶节点字典 — 完整 GroupPath → 对应层级的 XuiAvrgMDGroup
/// </summary>
private Dictionary<string, XuiAvrgMDGroup> _activeGroups = new Dictionary<string, XuiAvrgMDGroup>();
private Dictionary<string, XuiAvrgMDGroup> _leafGroupMap = new Dictionary<string, XuiAvrgMDGroup>();
[Tooltip("类型-预制体映射配置 ScriptableObject")]
public AvrgTypePrefabMappingConfig typePrefabMappingConfig;
@@ -170,13 +170,14 @@ namespace XericUI.ReflectionUIGenerator
});
}
// ============ 三阶段 GenerateUI ============
// ============ 四阶段 GenerateUI ============
/// <summary>
/// 生成 UI — 三阶段流程
/// 生成 UI — 四阶段流程
/// Phase A: 按 GroupPath 分组
/// Phase B: 创建 XuiAvrgMDGroup 组容器
/// Phase C: 在组容器内生成 UI 组件并绑定
/// Phase B: 构建路径树,创建嵌套的 XuiAvrgMDGroup 容器
/// Phase C: 在叶节点容器内生成 UI 组件并绑定
/// Phase D: 处理非编组 Entry 直接放进根目录
/// </summary>
public void GenerateUI()
{
@@ -186,21 +187,19 @@ namespace XericUI.ReflectionUIGenerator
return;
}
// Phase A: 收集并分组
// Phase A: 按完整 GroupPath 分组
var groupedEntries = new Dictionary<string, List<IFieldEntry>>();
var ungroupedEntries = new List<IFieldEntry>();
foreach (var entry in CurrentEntries)
{
string groupKey = !string.IsNullOrEmpty(entry.EntryName)
? GetGroupPathFromEntry(entry)
: null;
string groupPath = GetGroupPathFromEntry(entry);
if (!string.IsNullOrEmpty(groupKey))
if (!string.IsNullOrEmpty(groupPath))
{
if (!groupedEntries.ContainsKey(groupKey))
groupedEntries[groupKey] = new List<IFieldEntry>();
groupedEntries[groupKey].Add(entry);
if (!groupedEntries.ContainsKey(groupPath))
groupedEntries[groupPath] = new List<IFieldEntry>();
groupedEntries[groupPath].Add(entry);
}
else
{
@@ -208,18 +207,75 @@ namespace XericUI.ReflectionUIGenerator
}
}
// Phase B: 创建组容器
_activeGroups.Clear();
// Phase B: 构建树状嵌套组容器
_leafGroupMap.Clear();
BuildGroupTree(groupedEntries.Keys);
// Phase C: 在叶节点容器内生成 UI
foreach (var kvp in groupedEntries)
{
string groupPath = kvp.Key;
var entries = kvp.Value;
// 检查是否已创建(相同 GroupPath 只创建一个容器)
if (_activeGroups.ContainsKey(groupPath)) continue;
if (!_leafGroupMap.TryGetValue(groupPath, out var leafGroup)) continue;
var groupGo = new GameObject($"Group [{groupPath}]", typeof(RectTransform));
groupGo.transform.SetParent(transform, false);
foreach (var entry in entries)
{
GenerateSingleUI(entry, leafGroup.contentRoot);
}
}
// Phase D: 非编组 Entry 直接放进根
foreach (var entry in ungroupedEntries)
{
GenerateSingleUI(entry, transform);
}
}
// ============ 树状编组容器构建 ============
private void BuildGroupTree(IEnumerable<string> allGroupPaths)
{
var rootNode = new GroupTreeNode(null);
// Step 1: 将每个完整路径分裂后插入树
foreach (var fullPath in allGroupPaths)
{
if (string.IsNullOrEmpty(fullPath)) continue;
var segments = fullPath.Split('/');
var current = rootNode;
for (int i = 0; i < segments.Length; i++)
{
string seg = segments[i];
if (!current.Children.ContainsKey(seg))
current.Children[seg] = new GroupTreeNode(seg);
current = current.Children[seg];
}
}
// Step 2: 深度优先遍历,创建层级 GameObject
CreateGroupGameObjects(rootNode, null, transform, "");
}
private void CreateGroupGameObjects(
GroupTreeNode node,
XuiAvrgMDGroup parentGroup,
Transform parentTransform,
string accumulatedPath)
{
foreach (var kvp in node.Children)
{
string segName = kvp.Key;
var childNode = kvp.Value;
string childFullPath = string.IsNullOrEmpty(accumulatedPath)
? segName
: accumulatedPath + "/" + segName;
var groupGo = new GameObject($"Group [{childFullPath}]", typeof(RectTransform));
groupGo.transform.SetParent(parentTransform, false);
var rect = groupGo.GetComponent<RectTransform>();
rect.anchorMin = new Vector2(0, 1);
@@ -228,35 +284,32 @@ namespace XericUI.ReflectionUIGenerator
rect.sizeDelta = new Vector2(0, 0);
var group = groupGo.AddComponent<XuiAvrgMDGroup>();
int firstOrder = entries.Count > 0 ? entries[0].FieldOrder : 0;
group.Initialize(groupPath, firstOrder);
group.Initialize(childFullPath, 0, parentGroup);
_activeGroups[groupPath] = group;
}
// Phase C: 生成 UI 组件
foreach (var kvp in groupedEntries)
{
string groupPath = kvp.Key;
var entries = kvp.Value;
if (!_activeGroups.TryGetValue(groupPath, out var group)) continue;
foreach (var entry in entries)
// 叶节点映射到完整路径
if (childNode.Children.Count == 0)
{
GenerateSingleUI(entry, group.contentRoot);
_leafGroupMap[childFullPath] = group;
}
}
foreach (var entry in ungroupedEntries)
{
GenerateSingleUI(entry, transform);
CreateGroupGameObjects(childNode, group, group.contentRoot, childFullPath);
}
}
/// <summary>
/// 生成单个 Entry 的 UI
/// </summary>
private class GroupTreeNode
{
public string SegmentName { get; }
public Dictionary<string, GroupTreeNode> Children { get; }
public GroupTreeNode(string name)
{
SegmentName = name;
Children = new Dictionary<string, GroupTreeNode>();
}
}
// ============ 生成单个 UI ============
private void GenerateSingleUI(IFieldEntry entry, Transform parent)
{
if (entry is not XuiFieldTempEntry tempEntry) return;
@@ -276,7 +329,6 @@ namespace XericUI.ReflectionUIGenerator
uiInstance.name = entry.EntryName;
uiInstance.transform.SetParent(parent, false);
// 检查 XUIAvrgMDValue 是否存在
if (uiInstance.GetComponentInChildren<XUIAvrgMDValue>(true) == null)
{
Debug.LogError($"[Xuiavrg] 预制体 '{prefab.name}' 不符合生成要求:缺少 XUIAvrgMDValue 组件。请确保预制体根节点或其子节点包含 XUIAvrgMDValue 脚本。");
@@ -288,12 +340,8 @@ namespace XericUI.ReflectionUIGenerator
entry.OnEntryBind();
}
/// <summary>
/// 从 Entry 提取 GroupPath(通过 EntryName 判断)
/// </summary>
private string GetGroupPathFromEntry(IFieldEntry entry)
{
// 通过 Bindings 中的元数据找到 GroupPath
if (entry.Bindings != null && entry.Bindings.Count > 0)
{
var metaData = entry.Bindings[0].MetaData;
@@ -355,14 +403,14 @@ namespace XericUI.ReflectionUIGenerator
private void ClearGroups()
{
foreach (var kvp in _activeGroups)
foreach (var kvp in _leafGroupMap)
{
if (kvp.Value != null && kvp.Value.gameObject != null)
{
Destroy(kvp.Value.gameObject);
}
}
_activeGroups.Clear();
_leafGroupMap.Clear();
}
private void OnDestroy()
@@ -370,18 +418,5 @@ namespace XericUI.ReflectionUIGenerator
ClearAll();
}
// ============ 兼容旧 API ============
[Obsolete("Use BuildFromMetaDataSet + GenerateUI instead", false)]
public void SchemaHydration(object payload)
{
var metaDataSet = XuiavrgUtilities.ExtractAvrgMemberMetaData(payload);
var bindingCollection = metaDataSet.BuildMDVM(payload);
BuildFromMetaDataSet(metaDataSet, bindingCollection);
GenerateUI();
}
#endregion
}
}
+7 -4
View File
@@ -21,30 +21,33 @@ namespace XericUI.ReflectionUIGenerator
[Tooltip("编组排序值,越小越靠前")]
private int _groupOrder;
/// <summary>内容挂载点 — 子 UI 组件应放入此 Transform</summary>
/// <summary>内容挂载点 — 子 UI 组件或子组应放入此 Transform</summary>
public Transform contentRoot;
/// <summary>父编组(为 null 表示根组,直接挂在 Manager 下)</summary>
public XuiAvrgMDGroup ParentGroup { get; private set; }
/// <summary>编组路径对应的组名</summary>
public string GroupName => _groupName;
/// <summary>编组排序值</summary>
public int GroupOrder => _groupOrder;
/// <summary>上下文层级深度(如 "group1/subgroup" → depth=2)</summary>
/// <summary>上下文层级深度(如 "Stats/Speed" → depth=2)</summary>
public int ContextDepth { get; private set; }
/// <summary>
/// 初始化编组容器
/// </summary>
public void Initialize(string groupPath, int order)
public void Initialize(string groupPath, int order, XuiAvrgMDGroup parent = null)
{
_groupName = groupPath;
_groupOrder = order;
ParentGroup = parent;
ContextDepth = string.IsNullOrEmpty(groupPath) ? 0 : groupPath.Split('/').Length;
gameObject.name = $"Group [{groupPath}]";
// 如果没有指定 contentRoot,自动使用自身作为挂载点
if (contentRoot == null)
contentRoot = transform;
}
@@ -622,6 +622,7 @@ namespace XericUI.XTable.Rendering.Component
}
/// <summary>设置预制体(ContentType 自动设为 Prefab)</summary>
/// <remarks>注意是设置预制体而不是实例,设置后订阅单元格预制体生成事件 OnPrefabAcquire 处理获取事件</remarks>
public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
{
var data = TableData.GetOrCreateCell(row, col);