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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
[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 特性。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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

1
2
3
4
5
6
namespace Sirenix.OdinInspector.Editor.Drawers;

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

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

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

1
private ActionResolver? itemAction;

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

1
2
3
4
5
6
7
8
9
10
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 为空时,就继续走老逻辑,如果不为空,那就尝试调用解析的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
else
{
genericSelector.SelectionTree.EnumerateTree(x =>
{
itemAction?.Context.NamedValues.Set("item", x);
itemAction?.DoAction();

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

测试效果

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

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

并且修改字段的特性

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

image-20250513231709716

Welcome to my other publishing channels