本系列文章介绍UIElement知识点,欢迎访问😉

本系列文章使用的Unity版本为2019.3.7f1

自定义元素

定义一个自定义元素类

在使用UXML文件定义新元素之前,必须从VisualElement或它的一个子类派生一个新类,然后在这个新类中实现适当的功能。你的新类必须实现一个默认构造函数。

1
2
3
4
5
6
7
8
9
10
class StatusBar : VisualElement
{
public StatusBar()
{
m_Status = String.Empty;
}

string m_Status;
public string status { get; set; }
}

元素工厂

为了让UIElement在读取UXML文件时能够识别到该自定义元素类,必须为该元素类定义一个工厂。如果工厂不需要做一些特殊的事情,可以直接从UxmlFactoy派生工厂。建议将工厂类放在组件类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class StatusBar : VisualElement
{
public StatusBar()
{
m_Status = String.Empty;
}

string m_Status;
public string status { get; set; }

public new class UxmlFactory : UxmlFactory<StatusBar> {}

// ...
}

定义了这个工厂之后,可以在UXML文件中使用<StatusBar>元素。

添加元素

可以为一个新类定义UXML特征,并设置它的工厂来使用这些特征。例如,下面的代码演示了如何定义一个UXML traits类,将status属性初始化为StatusBar类的属性。status属性是从XML数据初始化的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class StatusBar : VisualElement
{
public StatusBar()
{
m_Status = String.Empty;
}

string m_Status;
public string status { get; set; }

public new class UxmlFactory : UxmlFactory<StatusBar, UxmlTraits> {}

//将UxmlTraits类放在StatusBar类中,Init()方法可以访问StatusBar的私有成员。
public new class UxmlTraits : VisualElement.UxmlTraits
{
//定义一个变量名m_Status初始值为status的UXML属性
UxmlStringAttributeDescription m_Status = new UxmlStringAttributeDescription { name = "status" };

public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
//返回一个空的IEnumerable,表示StatusBar不允许包含子元素
get { yield break; }
}

public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
//调用base.Init()来初始化基类属性。
base.Init(ve, bag, cc);
//从UXML解析器中读取属性包中status属性的值并设置status属性设置为该值。
//将元素的status初始化为m_Status的值,也就是"status"
((StatusBar)ve).status = m_Status.GetValueFromBag(bag, cc);
}
}

// ...
}

UxmlTraits有两个用途:工厂使用它来初始化新创建的对象。模式生成过程对其进行分析,以获得关于元素的信息。这些信息被转换成XML模式指令。

上面的代码示例使用UxmlStringAttributeDescription类声明了一个string属性。
UIElements支持以下类型的属性,每个都将c#类型链接到UXML类型

Attribute Attribute Value Type
UxmlStringAttributeDescription String
UxmlFloatAttributeDescription Float
UxmlDoubleAttributeDescription Double
UxmlIntAttributeDescription Int
UxmlLongAttributeDescription Long
UxmlBoolAttributeDescription Bool
UxmlColorAttributeDescription Color
UxmlEnumAttributeDescription<T> Enum

要使元素接受任何类型的子元素,必须重写uxmlChildElementsDescription属性。例如,要使StatusBar元素接受任何类型的子元素,必须指定uxmlChildElementsDescription属性,如下所示

1
2
3
4
5
6
7
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get
{
yield return new UxmlChildElementDescription(typeof(VisualElement));
}
}

定义命名空间

在c#中定义了一个新元素,就可以开始在UXML文件中使用这个元素了。如果新元素是在一个新的名称空间中定义的,那么应该为该名称空间定义一个前缀。命名空间前缀被声明为根<UXML>元素的属性,并在限定元素作用域时替换完整的命名空间名称。

例如将StatusBar放到MyVisualElement命名空间下:

1
2
3
4
5
6
7
8
9
10
11
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

namespace MyVisualElement
{
class StatusBar : VisualElement
{
//...省略
}
}

那么UXML中使用Status元素是:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
<!--此处要写完整的命名空间-->
<MyVisualElement.StatusBar />
</engine:UXML>

高级用法

自定义UXML name

可以自定义UXML名称通过重写IUxmlFactory.uxmlName和IUXmlFactory.uxmlQualifiedName属性。确保uxmlName在命名空间中是唯一的,uxmlQualifiedName在项目中是唯一的。如果两个名称都不唯一,则在尝试加载程序集时会引发异常。

1
2
3
4
5
6
7
8
9
10
11
12
public class FactoryWithCustomName : UxmlFactory<..., ...>
{
public override string uxmlName
{
get { return "UniqueName"; }
}

public override string uxmlQualifiedName
{
get { return uxmlNamespace + "." + uxmlName; }
}
}

为元素选择工厂

默认情况下,IUxmlFactory实例化一个元素并使用元素的名称选择该元素。

可以通过重写IUXmlFactory.AcceptsAttributeBag使选择过程考虑元素上的属性值。然后,工厂将检查元素属性,以决定是否可以为UXML元素实例化一个对象

如果VisualElement类是泛型的,这就很有用。在这种情况下,用于专门化类的类工厂可以检查XML类型属性的值。根据值的不同,可以接受或拒绝实例化。

在有多个工厂可以实例化一个元素的情况下,选择第一个注册的工厂。

重写基类元素属性的默认值

通过在派生的UxmlTraits类中设置其defaultValue来更改基类中声明的属性的默认值。

1
2
3
4
5
6
7
8
class MyElementTraits : VisualElement.UxmlTraits
{
public MyElementTraits()
{
//更改m TabIndex的默认值为0
m_TabIndex.defaultValue = 0;
}
}

接受任何属性

默认情况下,生成的XML模式声明元素可以具有任何属性。

除了在UxmlTraits类中声明的属性值之外,其他属性的值不受限制。这与XML验证器相反,XML验证器检查声明的属性值是否与其声明相匹配。

额外的属性包含在IUxmlAttributes包中,该包传递给IUxmlFactory.AcceptsAttributBag()和IUxmlFactory.Init()函数。工厂实现决定是否使用这些附加属性。默认行为是放弃附加属性。

如果放弃附加属性,就意味着这些附加属性没有附加到实例化的VisualElement,并且这些atttibute不能用UQuery查询。

在定义新元素时,可以通过设置UxmlTraits将接受的属性限制为那些显式声明的属性,
在UxmlTraits构造函数中将anyattribute属性设为false即可。

自定义元素案例

以下代码实现包含8种数据类型的VisualElement。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
using System.Collections.Generic;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

//命名空间
namespace MyVisualElement
{
//自定义元素类
class CustomVisualElement : VisualElement
{
//枚举
public enum Existance
{
None,
Good,
Bad
}

//8种数据类型的属性
public string stringAttr { get; set; }
public float floatAttr { get; set; }
public double doubleAttr { get; set; }
public int intAttr { get; set; }
public long longAttr { get; set; }
public bool boolAttr { get; set; }
public Color colorAttr { get; set; }
public Existance enumAttr { get; set; }

//工厂
public new class UxmlFactory : UxmlFactory<CustomVisualElement, UxmlTraits>
{
}

//使用UxmlTraits初始化新创建的元素对象
public new class UxmlTraits : VisualElement.UxmlTraits
{
UxmlStringAttributeDescription m_String = new UxmlStringAttributeDescription {name = "string-attr", defaultValue = "default_value"};
UxmlFloatAttributeDescription m_Float = new UxmlFloatAttributeDescription {name = "float-attr", defaultValue = 0.1f};
UxmlDoubleAttributeDescription m_Double = new UxmlDoubleAttributeDescription {name = "double-attr", defaultValue = 0.1};
UxmlIntAttributeDescription m_Int = new UxmlIntAttributeDescription {name = "int-attr", defaultValue = 2};
UxmlLongAttributeDescription m_Long = new UxmlLongAttributeDescription {name = "long-attr", defaultValue = 3};
UxmlBoolAttributeDescription m_Bool = new UxmlBoolAttributeDescription {name = "bool-attr", defaultValue = false};
UxmlColorAttributeDescription m_Color = new UxmlColorAttributeDescription {name = "color-attr", defaultValue = Color.red};
UxmlEnumAttributeDescription<Existance> m_Enum = new UxmlEnumAttributeDescription<Existance> {name = "enum-attr", defaultValue = Existance.Bad};

//该自定义的元素 无子元素
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get { yield break; }
}

//初始化
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
base.Init(ve, bag, cc);
var ate = ve as CustomVisualElement;

ate.Clear();

//添加8个属性对应的Field,以及初始化默认值
ate.stringAttr = m_String.GetValueFromBag(bag, cc);
ate.Add(new TextField("String") {value = ate.stringAttr});

ate.floatAttr = m_Float.GetValueFromBag(bag, cc);
ate.Add(new FloatField("Float") {value = ate.floatAttr});

ate.doubleAttr = m_Double.GetValueFromBag(bag, cc);
ate.Add(new DoubleField("Double") {value = ate.doubleAttr});

ate.intAttr = m_Int.GetValueFromBag(bag, cc);
ate.Add(new IntegerField("Integer") {value = ate.intAttr});

ate.longAttr = m_Long.GetValueFromBag(bag, cc);
ate.Add(new LongField("Long") {value = ate.longAttr});

ate.boolAttr = m_Bool.GetValueFromBag(bag, cc);
ate.Add(new Toggle("Toggle") {value = ate.boolAttr});

ate.colorAttr = m_Color.GetValueFromBag(bag, cc);
ate.Add(new ColorField("Color") {value = ate.colorAttr});

ate.enumAttr = m_Enum.GetValueFromBag(bag, cc);
var en = new EnumField("Enum");
en.Init(m_Enum.defaultValue);
en.value = ate.enumAttr;
ate.Add(en);
}
}
}
}

UXML:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
<!--C#元素类定义了命名空间-->
<MyVisualElement.CustomVisualElement />
</engine:UXML>

UXML中也可以直接定义属性值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:engine="UnityEngine.UIElements"
xmlns:editor="UnityEditor.UIElements"
xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
<!--C#元素类定义了命名空间-->
<!--举例说明:string-attr 得跟UxmlTraits中定义的UxmlAttributeDescription的name保持一致-->
<MyVisualElement.CustomVisualElement
string-attr="my-string"
float-attr="4.2"
double-attr="4.3"
int-attr="4"
long-attr="423"
bool-attr="true"
color-attr="#CA7C03FF"
enum-attr="Good"/>
</engine:UXML>

C# Load Code:

1
2
var customElement = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/CustomElement.uxml");
root.Add(customElement.CloneTree());

自定义元素示意图

参考文章

更多的UIElement知识点文章,还在进行中,如有相关内容需要介绍的,可以在下方留言,抽空更新文章~

以上知识分享,如有错误,欢迎指出,共同学习,共同进步,欢迎留言评论!