From fe5ae4726cf4a8d78b0dc0db6ba50d63dfb9c232 Mon Sep 17 00:00:00 2001 From: lrc <571244399@qq.com> Date: Sat, 25 Jul 2026 23:56:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E7=B4=A7=E5=90=84=E7=BB=84=E4=BB=B6?= =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scripts/AvrgBindingTrie.cs.meta | 11 ++ .../Scripts/AvrgSubPropertyAccessor.cs.meta | 11 ++ .../Scripts/ValidationState.cs.meta | 11 ++ .../AvrgValues/XUIAvrgMDDropdownValue.cs | 49 ++++++- .../AvrgValues/XUIAvrgMDLabelValue.cs | 44 +++++- .../AvrgValues/XUIAvrgMDSliderValue.cs | 90 +++++++++--- .../AvrgValues/XUIAvrgMDToggleGroupValue.cs | 136 ++++++++++++++---- .../OverrideUI/AvrgValues/XUIAvrgMDValue.cs | 85 ++++++----- 8 files changed, 342 insertions(+), 95 deletions(-) create mode 100644 Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgBindingTrie.cs.meta create mode 100644 Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgSubPropertyAccessor.cs.meta create mode 100644 Runtime/InterfaceAttributeReflectionGenerator/Scripts/ValidationState.cs.meta diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgBindingTrie.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgBindingTrie.cs.meta new file mode 100644 index 0000000..fe68f26 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgBindingTrie.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0fbb9c5ea2b8b5644a49559e7b6260e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgSubPropertyAccessor.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgSubPropertyAccessor.cs.meta new file mode 100644 index 0000000..70496d4 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgSubPropertyAccessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0ab49f7e492a8d448cdcc9f214325f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/ValidationState.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/ValidationState.cs.meta new file mode 100644 index 0000000..eb94e88 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/ValidationState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e20b793a32d670a4d95e20189dd27694 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDDropdownValue.cs b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDDropdownValue.cs index 20dbdb9..05344ef 100644 --- a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDDropdownValue.cs +++ b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDDropdownValue.cs @@ -8,18 +8,24 @@ namespace XericUI.ReflectionUIGenerator /// /// Dropdown 值组件 — 处理 string / int / enum 类型的下拉选择交互 /// 预设选项来源:元数据的 PresetOptions 或 enum 类型的枚举名 + /// + /// 选项动态创建:OnPoolGet 时根据元数据 PresetOptions 构建选项列表, + /// BindToAccessor 时通过 AddOptions() 填充到 Dropdown 组件。 /// [AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDDropdownValue")] public class XUIAvrgMDDropdownValue : XUIAvrgMDValue { - [SerializeField] - [Tooltip("关联的 Dropdown 组件(未设置则自动查找)")] + [SerializeField, Tooltip("关联的 Dropdown 组件(未设置则自动查找)")] #if dUI_TextMeshPro private TMPro.TMP_Dropdown _dropdown; #else private Dropdown _dropdown; #endif + [Header("选项动态创建")] + [SerializeField, Tooltip("选项项预制体 Transform(可选,用于自定义选项创建逻辑)")] + private Transform _optionItemPrefab; + private List _options; private void Awake() @@ -37,16 +43,55 @@ namespace XericUI.ReflectionUIGenerator { base.OnPoolGet(metaData); _options = BuildOptions(metaData?.ValueType); + + // 动态创建选项对象(子类可覆写) + CreateOptionItems(); } public override void OnPoolRelease() { if (_dropdown != null) _dropdown.onValueChanged.RemoveAllListeners(); + + DestroyOptionItems(); _options = null; base.OnPoolRelease(); } + // ============ 动态选项创建 ============ + + /// + /// 根据预设选项动态创建选项子对象 + /// 子类可覆写以实现自定义选项创建(如图标、颜色等) + /// + protected virtual void CreateOptionItems() + { + if (_optionItemPrefab == null || _options == null) return; + + for (int i = 0; i < _options.Count; i++) + { + var item = Instantiate(_optionItemPrefab, _optionItemPrefab.parent); + item.name = $"Option_{i}_{_options[i]}"; + item.gameObject.SetActive(true); + } + } + + /// + /// 销毁动态创建的选项子对象 + /// + protected virtual void DestroyOptionItems() + { + if (_optionItemPrefab == null) return; + + var parent = _optionItemPrefab.parent; + for (int i = parent.childCount - 1; i >= 0; i--) + { + var child = parent.GetChild(i); + if (child != _optionItemPrefab) + Destroy(child.gameObject); + } + } + // ============ 核心绑定 ============ public override void BindToAccessor( diff --git a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDLabelValue.cs b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDLabelValue.cs index 85c63b7..46e3a9d 100644 --- a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDLabelValue.cs +++ b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDLabelValue.cs @@ -1,19 +1,27 @@ using UnityEngine; -using UnityEngine.UI; namespace XericUI.ReflectionUIGenerator { /// /// Label 只读值组件 — 纯文本展示,无交互控件 + /// 拥有自己的 _valueDisplayText 用于显示值 /// [AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDLabelValue")] public class XUIAvrgMDLabelValue : XUIAvrgMDValue { + [SerializeField, Tooltip("值显示文本 — 显示当前字段的值")] #if dUI_TextMeshPro - [SerializeField, Tooltip("关联的 TMP_Text 组件(标签文本 + 值文本共用一个 Text 时使用)")] - private TMPro.TMP_Text _tmpText; + private TMPro.TMP_Text _valueDisplayText; +#else + private UnityEngine.UI.Text _valueDisplayText; #endif + public override void OnPoolRelease() + { + if (_valueDisplayText != null) _valueDisplayText.text = ""; + base.OnPoolRelease(); + } + public override void BindToAccessor( IAvrgValueAccessor accessor, AvrgMvvmBinding binding, @@ -33,5 +41,35 @@ namespace XericUI.ReflectionUIGenerator var initialValue = accessor.GetValue(); UpdateValueDisplay(initialValue, ValidationState.Normal); } + + protected override void UpdateValueDisplay(object value, ValidationState state) + { + if (_valueDisplayText != null) + { + _valueDisplayText.text = CurrentAccessor?.FormatDisplay(value) ?? value?.ToString() ?? ""; + + _valueDisplayText.color = state == ValidationState.Invalid + ? Color.red + : state == ValidationState.Warning + ? new Color(1f, 0.92f, 0.016f) + : Color.white; + } + + base.UpdateValueDisplay(value, state); + } + + protected override void OnValidationStateChanged(ValidationState state) + { + base.OnValidationStateChanged(state); + + if (_valueDisplayText != null) + { + _valueDisplayText.color = state == ValidationState.Invalid + ? Color.red + : state == ValidationState.Warning + ? new Color(1f, 0.92f, 0.016f) + : Color.white; + } + } } } diff --git a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDSliderValue.cs b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDSliderValue.cs index 5f3f6d5..ab48756 100644 --- a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDSliderValue.cs +++ b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDSliderValue.cs @@ -15,17 +15,33 @@ namespace XericUI.ReflectionUIGenerator private Slider _slider; [Header("Slider 专属插槽")] + [SerializeField, Tooltip("值显示文本 — 显示当前 Slider 数值")] +#if dUI_TextMeshPro + private TMPro.TMP_Text _valueDisplayText; +#else + private UnityEngine.UI.Text _valueDisplayText; +#endif + + [SerializeField, Tooltip("最小值文本 — 显示 Slider 下限")] +#if dUI_TextMeshPro + private TMPro.TMP_Text _minValueText; +#else + private UnityEngine.UI.Text _minValueText; +#endif + + [SerializeField, Tooltip("最大值文本 — 显示 Slider 上限")] +#if dUI_TextMeshPro + private TMPro.TMP_Text _maxValueText; +#else + private UnityEngine.UI.Text _maxValueText; +#endif + [SerializeField, Tooltip("增加按钮(点击按步进值 +)")] private Button _incrementButton; [SerializeField, Tooltip("减少按钮(点击按步进值 -)")] private Button _decrementButton; -#if dUI_TextMeshPro - [SerializeField, Tooltip("值显示标签(可选,未设置则不更新)")] - private TMPro.TMP_Text _valueLabel; -#endif - private const float DefaultStep = 0.1f; private void Awake() @@ -51,7 +67,6 @@ namespace XericUI.ReflectionUIGenerator public override void OnPoolRelease() { - // 清除按钮监听 if (_incrementButton != null) _incrementButton.onClick.RemoveAllListeners(); if (_decrementButton != null) @@ -59,6 +74,11 @@ namespace XericUI.ReflectionUIGenerator if (_slider != null) _slider.onValueChanged.RemoveAllListeners(); + // 重置子类文本 + if (_valueDisplayText != null) _valueDisplayText.text = ""; + if (_minValueText != null) _minValueText.text = ""; + if (_maxValueText != null) _maxValueText.text = ""; + base.OnPoolRelease(); } @@ -74,6 +94,9 @@ namespace XericUI.ReflectionUIGenerator if (binding == null || accessor == null || _slider == null) return; + // 初始化上下限文本 + ApplyRangeTexts(); + // 增减按钮 if (_incrementButton != null) _incrementButton.onClick.AddListener(OnIncrementClicked); @@ -87,10 +110,7 @@ namespace XericUI.ReflectionUIGenerator binding.WriteFromUI(convertedVal); scheduler?.MarkUIDirty(binding); -#if dUI_TextMeshPro - if (_valueLabel != null) - _valueLabel.text = accessor.FormatDisplay(convertedVal); -#endif + RefreshValueText(convertedVal); }); // 数据 → UI(带验证状态) @@ -99,12 +119,9 @@ namespace XericUI.ReflectionUIGenerator float sliderVal = Convert.ToSingle(value); _slider.SetValueWithoutNotify(sliderVal); -#if dUI_TextMeshPro - if (_valueLabel != null) - _valueLabel.text = accessor.FormatDisplay(value); -#endif + RefreshValueText(value); + ApplyValueTextColor(state); - // 委托基类处理标签合成 + 值文本 base.UpdateValueDisplay(value, state); }; @@ -114,16 +131,45 @@ namespace XericUI.ReflectionUIGenerator // 首次同步 var initialValue = accessor.GetValue(); if (initialValue != null) - { _slider.SetValueWithoutNotify(Convert.ToSingle(initialValue)); -#if dUI_TextMeshPro - if (_valueLabel != null) - _valueLabel.text = accessor.FormatDisplay(initialValue); -#endif - } + + RefreshValueText(initialValue); UpdateValueDisplay(initialValue ?? 0f, ValidationState.Normal); } + // ============ 值显示 ============ + + protected override void UpdateValueDisplay(object value, ValidationState state) + { + ApplyValueTextColor(state); + base.UpdateValueDisplay(value, state); + } + + private void RefreshValueText(object value) + { + if (_valueDisplayText == null || CurrentAccessor == null) return; + _valueDisplayText.text = CurrentAccessor.FormatDisplay(value); + } + + private void ApplyValueTextColor(ValidationState state) + { + if (_valueDisplayText == null) return; + + _valueDisplayText.color = state == ValidationState.Invalid + ? Color.red + : state == ValidationState.Warning + ? new Color(1f, 0.92f, 0.016f) + : Color.white; + } + + private void ApplyRangeTexts() + { + if (_minValueText != null) + _minValueText.text = CurrentAccessor?.FormatDisplay(_slider.minValue) ?? _slider.minValue.ToString(); + if (_maxValueText != null) + _maxValueText.text = CurrentAccessor?.FormatDisplay(_slider.maxValue) ?? _slider.maxValue.ToString(); + } + // ============ 步进值 ============ private float GetStepValue() @@ -169,6 +215,8 @@ namespace XericUI.ReflectionUIGenerator }; } } + + ApplyValueTextColor(state); } private static object ConvertValueForAccessor(float val, Type targetType) diff --git a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDToggleGroupValue.cs b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDToggleGroupValue.cs index 9c4ff8a..e19078e 100644 --- a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDToggleGroupValue.cs +++ b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDToggleGroupValue.cs @@ -8,26 +8,125 @@ namespace XericUI.ReflectionUIGenerator /// /// ToggleGroup 值组件 — 处理 string / int / enum 类型的单选组交互 /// 适用于标记了 [XuiavrgFieldToggleGroupValue] 的字段 + /// + /// 选项动态创建:OnPoolGet 时根据元数据 PresetOptions 数量, + /// 从 _toggleItemPrefab 实例化 Toggle 子对象。 /// [AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleGroupValue")] public class XUIAvrgMDToggleGroupValue : XUIAvrgMDValue { - [SerializeField] - [Tooltip("关联的 ToggleGroup 组件(未设置则自动查找)")] + [SerializeField, Tooltip("关联的 ToggleGroup 组件(未设置则自动查找)")] private ToggleGroup _toggleGroup; + [Header("选项动态创建")] + [SerializeField, Tooltip("Toggle 项预制体 — 在 OnPoolGet 时按预设选项数量实例化")] + private Transform _toggleItemPrefab; + + [SerializeField, Tooltip("选项容器 Transform — 动态创建的 Toggle 挂载到此节点下")] + private Transform _optionsContainer; + private List _toggles = new List(); private List _optionLabels = new List(); private void Awake() { if (_toggleGroup == null) _toggleGroup = GetComponent(); - if (_toggleGroup != null) + } + + // ============ 对象池生命周期 ============ + + public override void OnPoolGet(AvrgMemberMetaData metaData) + { + base.OnPoolGet(metaData); + + // 构建选项列表 + BuildOptions(metaData?.ValueType); + + // 动态创建 Toggle 项 + CreateToggleItems(); + } + + public override void OnPoolRelease() + { + // 清理动态创建的 Toggle + foreach (var toggle in _toggles) { - _toggles = _toggleGroup.GetComponentsInChildren(true).ToList(); + if (toggle != null) + toggle.onValueChanged.RemoveAllListeners(); + } + DestroyToggleItems(); + + _toggles.Clear(); + _optionLabels.Clear(); + + base.OnPoolRelease(); + } + + // ============ 动态 Toggle 创建 ============ + + /// + /// 根据 _optionLabels 数量,从 _toggleItemPrefab 实例化 Toggle 子对象 + /// + private void CreateToggleItems() + { + var parent = _optionsContainer != null ? _optionsContainer : transform; + if (_toggleItemPrefab == null || _optionLabels.Count == 0) return; + + for (int i = 0; i < _optionLabels.Count; i++) + { + var item = Instantiate(_toggleItemPrefab, parent); + item.name = $"Toggle_{i}_{_optionLabels[i]}"; + item.gameObject.SetActive(true); + + var toggle = item.GetComponent(); + if (toggle != null) + { + toggle.group = _toggleGroup; + _toggles.Add(toggle); + + // 设置标签文本 + var label = item.GetComponentInChildren(true); + if (label != null) + label.text = _optionLabels[i]; + } } } + /// + /// 销毁动态创建的所有 Toggle 子对象 + /// + private void DestroyToggleItems() + { + var parent = _optionsContainer != null ? _optionsContainer : transform; + + foreach (var toggle in _toggles) + { + if (toggle != null) + Destroy(toggle.gameObject); + } + } + + private void BuildOptions(System.Type valueType) + { + _optionLabels.Clear(); + + // 1. 从元数据 PresetOptions + if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0) + { + _optionLabels.AddRange(BoundMetaData.PresetOptions); + return; + } + + // 2. enum 类型 + if (valueType != null && valueType.IsEnum) + { + _optionLabels.AddRange(System.Enum.GetNames(valueType)); + return; + } + } + + // ============ 核心绑定 ============ + public override void BindToAccessor( IAvrgValueAccessor accessor, AvrgMvvmBinding binding, @@ -36,10 +135,14 @@ namespace XericUI.ReflectionUIGenerator CurrentAccessor = accessor; CurrentBinding = binding; - if (binding == null || accessor == null || _toggleGroup == null) return; + if (binding == null || accessor == null || _toggleGroup == null || _toggles.Count == 0) return; - // 构建选项 - BuildOptions(accessor.ValueType); + // 只读模式下禁用所有 Toggle + if (accessor.IsReadOnly) + { + foreach (var t in _toggles) + t.interactable = false; + } // 为每个 Toggle 注册事件 for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++) @@ -88,25 +191,6 @@ namespace XericUI.ReflectionUIGenerator } } - private void BuildOptions(System.Type valueType) - { - _optionLabels.Clear(); - - // 1. 从元数据 PresetOptions - if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0) - { - _optionLabels.AddRange(BoundMetaData.PresetOptions); - return; - } - - // 2. enum 类型 - if (valueType != null && valueType.IsEnum) - { - _optionLabels.AddRange(System.Enum.GetNames(valueType)); - return; - } - } - private static object ConvertOptionToValue(string option, System.Type targetType) { if (targetType == typeof(string)) return option; diff --git a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDValue.cs b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDValue.cs index 9ddee2a..f8cc5c9 100644 --- a/Runtime/OverrideUI/AvrgValues/XUIAvrgMDValue.cs +++ b/Runtime/OverrideUI/AvrgValues/XUIAvrgMDValue.cs @@ -13,7 +13,8 @@ namespace XericUI.ReflectionUIGenerator /// 3. 通过 virtual BindToAccessor() 让子类自行管理 UI 交互事件 /// 4. 对象池生命周期:OnPoolGet(meta) 初始化、OnPoolRelease() 重置 /// 5. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配 - /// 6. 通过 UpdateValueDisplay() 合成标签文本(名称 + 只读 + 警告标记) + /// 6. 基类统一管理标签文本(名称 + 只读 + 警告标记)和单位可见性 + /// 值显示由各子类自行实现 /// [AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")] public class XUIAvrgMDValue : MonoBehaviour @@ -49,7 +50,7 @@ namespace XericUI.ReflectionUIGenerator // ============ UI 文本插槽 ============ - [Header("UI 文本插槽")] + [Header("UI 基类插槽")] [SerializeField, Tooltip("字段标签文本 — 显示 \"名称 (只读) ⚠警告\",为 null 则不显示标签")] #if dUI_TextMeshPro protected TMPro.TMP_Text _fieldLabelText; @@ -57,13 +58,6 @@ namespace XericUI.ReflectionUIGenerator protected UnityEngine.UI.Text _fieldLabelText; #endif - [SerializeField, Tooltip("值显示文本 — 可为 null(Slider/Color/Texture 子类不依赖文本)")] -#if dUI_TextMeshPro - protected TMPro.TMP_Text _valueDisplayText; -#else - protected UnityEngine.UI.Text _valueDisplayText; -#endif - [SerializeField, Tooltip("单位文本 — 可为 null")] #if dUI_TextMeshPro protected TMPro.TMP_Text _unitText; @@ -71,6 +65,9 @@ namespace XericUI.ReflectionUIGenerator protected UnityEngine.UI.Text _unitText; #endif + [SerializeField, Tooltip("单位容器 Transform — 单位文本为空时 SetActive(false) 隐藏")] + protected Transform _unitTransform; + // ============ 公共属性 ============ /// 绑定的元数据引用 @@ -102,7 +99,8 @@ namespace XericUI.ReflectionUIGenerator BoundMetaData = metaData; CurrentValidationState = ValidationState.Normal; - // 子类覆写点 + // 单位可见性:无单位文本则隐藏单位节点 + ApplyUnitVisibility(); } /// @@ -118,8 +116,10 @@ namespace XericUI.ReflectionUIGenerator // 重置 UI 文本 if (_fieldLabelText != null) _fieldLabelText.text = ""; - if (_valueDisplayText != null) _valueDisplayText.text = ""; if (_unitText != null) _unitText.text = ""; + + // 重置单位可见性 + if (_unitTransform != null) _unitTransform.gameObject.SetActive(true); } // ============ 核心绑定接口 ============ @@ -155,34 +155,17 @@ namespace XericUI.ReflectionUIGenerator /// /// 根据值和验证状态更新 UI 显示 - /// 子类可覆写以处理专属组件更新(Slider.value, Image.color 等) - /// 基类负责合成标签文本、值文本颜色、单位文本 + /// 子类必须覆写以处理专属组件更新(Slider.value, Image.color 等), + /// 并在覆写中调用 base.UpdateValueDisplay(value, state) 以刷新标签。 /// protected virtual void UpdateValueDisplay(object value, ValidationState state) { // 1. 字段标签合成 if (_fieldLabelText != null) - { - string label = BuildLabel(state); - _fieldLabelText.text = label; - } + _fieldLabelText.text = BuildLabel(state); - // 2. 值显示 - if (_valueDisplayText != null) - { - string display = CurrentAccessor?.FormatDisplay(value) ?? value?.ToString() ?? ""; - _valueDisplayText.text = display; - - _valueDisplayText.color = state == ValidationState.Invalid - ? Color.red - : state == ValidationState.Warning - ? new Color(1f, 0.92f, 0.016f) // yellow - : Color.white; - } - - // 3. 单位文本 - if (_unitText != null) - _unitText.text = BoundMetaData?.UnitLabelName ?? ""; + // 2. 单位文本 + 可见性 + ApplyUnitText(); } /// @@ -211,6 +194,32 @@ namespace XericUI.ReflectionUIGenerator return sb.ToString(); } + /// + /// 更新单位文本并控制可见性 + /// + protected void ApplyUnitText() + { + string unit = BoundMetaData?.UnitLabelName; + bool hasUnit = !string.IsNullOrEmpty(unit); + + if (_unitText != null) + _unitText.text = hasUnit ? unit : ""; + + if (_unitTransform != null) + _unitTransform.gameObject.SetActive(hasUnit); + } + + /// + /// 仅刷新单位可见性(不更新文本内容) + /// + protected void ApplyUnitVisibility() + { + if (_unitTransform == null) return; + + string unit = BoundMetaData?.UnitLabelName; + _unitTransform.gameObject.SetActive(!string.IsNullOrEmpty(unit)); + } + // ============ 验证状态回调 ============ /// @@ -224,16 +233,6 @@ namespace XericUI.ReflectionUIGenerator // 刷新标签文本(警告/无效标记在标签上) if (_fieldLabelText != null) _fieldLabelText.text = BuildLabel(state); - - // 值文本颜色 - if (_valueDisplayText != null) - { - _valueDisplayText.color = state == ValidationState.Invalid - ? Color.red - : state == ValidationState.Warning - ? new Color(1f, 0.92f, 0.016f) - : Color.white; - } } // ============ 元数据广播 ============