← 所有文章

基于ValueDropdown拓展以支持自定义icon

之前遇到过一个需求是要在下拉框的目录做一个标记来指示状态,应该很自然就可以想到能不能修改一下 icon,比如把文件夹的那个icon改成其他颜色的,这样会比修改显示名字毫无疑问更加显眼。

不过其实想要进行更大限度的修改是完全可以的,只需要魔改一下 ValueDropdown 就行。

先说一下简单的解决先前提到的需求的办法吧——改显示名字。

一般是使用 ValueDropdownList<T> 或者 IEnumerable<ValueDropdownItem<T>> (二者是等价的) 作为函数的返回值,比如:

[ShowInInspector, LabelText("测试下拉框")]
[ValueDropdown(nameof(GetAllowIndexs))]
private int Index;

private ValueDropdownList<int> GetAllowIndexs()
{
    var list = new ValueDropdownList<int>();
    for (int i = 0; i < 10; i++)
    {
        list.Add($"The {i + 1} item", i);
    }
    return list;
}

效果如下,可以给每个 value 都指定自定义的名字,不过更复杂的操作似乎就不行了(或者我没有找到?)

测试下拉框

拓展 ValueDropdown

既然原本的 ValueDropdown 无法满足需求,那就自己搓一个吧,写一个类继承原来的 ValueDropdown 特性。

然后我们再原来的基础上,再添加一个回调字段

#if UNITY_EDITOR && ODIN_INSPECTOR

using System;
using System.Diagnostics;

namespace Sirenix.OdinInspector;

[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
[Conditional("UNITY_EDITOR")]
public class CustomValueDropdownAttribute : ValueDropdownAttribute
{
    public string ItemAction;

    public CustomValueDropdownAttribute(string valueGetter, string itemAction)
        : base(valueGetter)
    {
        ItemAction = itemAction;
    }
}

#endif

然后自定义 attribute drawer

namespace Sirenix.OdinInspector.Editor.Drawers;

public sealed class CustomValueDropdownAttributeDrawer : OdinAttributeDrawer<CustomValueDropdownAttribute>
{
    // ...
}

简单看一下 ValueDropdownAttributeDrawer 的实现,就会发现是基于 OdinMenuTree 做的,每一项其实都是一个 OdinMenuItem,所以不妨让我们的自定义的回调的参数是 OdinMenuItem,这样就可以实现更大限度的自定义每一项。

添加一个 ActionResolver 字段原来解析传入的方法名

private ActionResolver? itemAction;

然后在初始化函数中添加解析逻辑,(也别忘了设置错误信息)

if (base.Attribute.ItemAction is not null)
{
    itemAction = ActionResolver.Get(
        base.Property,
        base.Attribute.ItemAction,
		new[] { new ActionResolvers.NamedValue("item", typeof(OdinMenuItem)) }
    );
}

error = rawGetter.ErrorMessage ?? itemAction?.ErrorMessage;

翻一下逻辑,会发现设置 icon 是通过 AddThumbnailIcons(preferAssetPreviewAsIcon: true); 调用。所以我们修改一下这部分的逻辑,当 itemAction 为空时,就继续走老逻辑,如果不为空,那就尝试调用解析的逻辑

else
{
    genericSelector.SelectionTree.EnumerateTree(x =>
    {
        itemAction?.Context.NamedValues.Set("item", x);
        itemAction?.DoAction();

        if (x.Icon == null)
        {
            x.AddThumbnailIcon(preferAssetPreviewAsIcon: true);
        }
    });
}

测试效果

在原来的测试代码那里添加一个函数

private void HandleItem(OdinMenuItem item)
{
    item.Icon = EditorIcons.Flag.Active;
}

并且修改字段的特性

[CustomValueDropdown(nameof(GetAllowIndexs), nameof(HandleItem))]

image-20250513231709716

· · ·

感谢阅读。愿这份记录也能帮到你。

继续探索其他文章 ↗