UI Toolkit是Unity最新的UI系统,它主要被设计用来优化不同平台的性能,此项技术是基于标准的web技术开发的(standard web technologies),既可以使用UI Toolkit来拓展Unity Editor,也可以在打包出来的游戏和应用里使用Runtime的UI(但需要安装UI Toolkit Package)
UI Toolkit包括以下内容:
Unity想推荐UI Toolkit成为新项目的UI系统,但是它跟传统的uGUI和IMGUI相比,还是少了一些功能,后面会再提到。
UI Toolkit是一系列用于创建UI的资源、函数、特性和工具的集合,它可以被用过来创建常规的UI,也可以用来拓展Unity Editor、制作Runtime的Debug工具和创建Runtime的游戏UI。
UI Toolkit受standard web technologies启发得到,很多核心的概念是类似的。
UI Toolkit分为以下三类:
UI Toolkit的核心是一个retained-mode UI system based on recognized web technologie。它支持stylesheets,和dynamic and contextual event handling.
UI System有以下内容:
UI Assets也就是UI Toolkit里用到的资源文件,UI Toolkit提供了两种资源文件来帮助构建UI,与web应用类似:
UXML全称为Unity eXtensible Markup Lauguage,是受HTML和XML启发得到的一种markup(标记)语言,用于定义UI结构和可复用的UI模板,Unity推荐使用UXML来创建UI,而不是在C#脚本里进行
USS全称为Unity Style Sheets:可以对UI使用可视的style和behaviours,与web的CSS类似,跟上面相同,Unity推荐用USS文件来定义style,而不是直接在C#脚本里对style这个property进行修改
提供了以下工具和资源:
UI Toolkit有两种获取方法,或者说有两个版本:
二者的区别如下:
该选择UI Toolkit两个版本的哪一个
如果相关UI只会在Editor下使用的话,那么使用内置的UI Toolkit,如果该UI需要既能在Editor,也能在Runtime下使用的话,那么使用对应的Package的版本,而且对应的版本也能安装最新的
安装 UI Toolkit package
打开Unity Editor的Package Manager:
UI Toolkit里UI的最基本构建单元被称为Visual Element,这些elements会被排序,形成一个有层次结构的树,称为Visual Tree,下图是一个例子:
Visual elements
VisualElement类是所有出现在Visual Tree里节点的基类,它定义了通用的properties,比如style、layout data和event handles。可以使用
stylesheet来自定义Visual Element的形状,也可以使用event callback来自定义Visual Element的行为
VisualElement的派生类可以再添加behaviour和功能,比如UI Controls,下面的这些都是基于Visual Element派生出来的:
后面还会介绍更多的内置的Controls
Panels
panel是Visual Tree的父object,对于一个Visual Tree,它需要连接到panel上才能被渲染出来,所有的Panels都从属于Window,比如EditorWindow,Panel除了处理Visual Tree的渲染外,还会处理相关的focus control和event dispatching。
每一个在Visual Tree里的Visual Element都会记录该Panel的引用,VisualElement对象里叫panel的property可以用于检测Element是否与Panel相连,若panel为null说明不相连
Draw Order
Visual Tree里默认是按深度遍历的顺序绘制Element的,如果想要改顺序,可以使用以下函数:
VisualElement e;
// 注意,下面的front和back都是视觉上的绘制关系,front意味着重叠部分不会被遮挡
// 会把该元素移到它原本的parent的children列表的最后面,所以该元素最后画,所以在top
e.BringToFront();
// 同上,正好反过来
e.SendToBack();
// 在parent的childrenn列表里,把e放到sbling的前面,即先画e再画sibling,所以e在底层
e.PlaceBehind(UIElements.VisualElement sibling);
// 同上,正好反过来
e.PlaceInFront(UIElements.VisualElement sibling);
Coordinate and position systems
UI Toolkit有一个强大的layout系统,根据每一个Visual Element里名为style的property,就能自动计算出每个Element的位置和size,后面还会详细提到Layout Engine.
UI Toolkit有两种坐标(coordinates):
设置一个Element的Coordinates的方法如下所示:
var newElement = new VisualElement();
newElement.style.position = Position.Relative;
newElement.style.left = 15;
newElement.style.top = 35;
在实际计算pos的时候,layout system会为每个element计算位置和size,再把前面的relative或absolute的coordinate offset加进去,最后的结果计算出来,存到element.layout里(类型是Rect)
The layout.position is expressed in points, relative to the coordinate space of its parent.
VisualElement类还有一个继承的Property,叫做ITransform,修改它可以添加额外的Local的position和rotation的变化,相关的变化不会显示在layout属性里,ITransform默认是Identity.
VisualElement.worldBounds代表Element在窗口空间的最终坐标bounds,它既考虑了layout,也考虑了ITransform,This position includes the height of the header of the window.
下面介绍一个例子,使用内置的UI Toolkit来创建Editor下的窗口。首先可以创建一个脚本,脚本内容如下:
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class PositioningTestWindow : EditorWindow
{
[MenuItem("Window/UI Toolkit/Positioning Test Window")]
public static void ShowExample()
{
var wnd = GetWindow<PositioningTestWindow>();
wnd.titleContent = new GUIContent("Positioning Test Window");
}
public void CreateGUI()
{
// 创建两个数据一模一样的Element, 注意这里没有指定位置,因为位置是Layout系统自己算的
for (int i = 0; i < 2; i++)
{
// 创建两个Element, 为一个正方形, 背景是灰色
var temp = new VisualElement();
temp.style.width = 70;
temp.style.height = 70;
// marginBottom代表当Layout系统计算布局时, 此Element下方会预留20个像素的距离
temp.style.marginBottom = 20;
temp.style.backgroundColor = Color.gray;
rootVisualElement.Add(temp);
}
}
}
继续补充CreateGUI代码,现在画一个Label,而且更改它的style里的pos,代码如下:
public void CreateGUI()
{
// 创建两个数据一模一样的Element, 注意这里没有指定位置,因为位置是Layout系统自己算的
...//原本的不变
// 创建一个Label, Label是VisualElement的派生类
var relative = new Label("Relative\nPos\n25, 0");
// relative.style.position = Position.Relative;// 默认的就是Relative的方式, 所以不用刻意去写
relative.style.width = 70;
relative.style.height = 70;
relative.style.left = 25;
relative.style.marginBottom = 20;
relative.style.backgroundColor = Color.red;
rootVisualElement.Add(relative);
}
现在的结果变成了下图所示的样子,可以看到,原本Label应该是跟之前的一样,往下20个像素绘制的,但是这里有style.left = 25,所以在原本的基础上,加上offset(25, 0),得到最后右移的位置:
展示完了Relative的方式,下面再看看Absolute的例子,代码也是类似:
public void CreateGUI()
{
...// 画原本三个Element的代码不变
// 又画两个相同的方块进行对比
for (int i = 0; i < 2; i++)
{
var temp = new VisualElement();
temp.style.width = 70;
temp.style.height = 70;
temp.style.marginBottom = 20;
temp.style.backgroundColor = Color.gray;
rootVisualElement.Add(temp);
}
// 绘制Absolute类型的方块:Absolute Positioning
var absolutePositionElement = new Label("Absolute\nPos\n25, 25");
// 类型是Absolute, 基准点是parent element, 其parent element就是窗口里的rootVisualElement
absolutePositionElement.style.position = Position.Absolute;
absolutePositionElement.style.top = 25; // 设置上方间距
absolutePositionElement.style.left = 25; // 设置左边间距
absolutePositionElement.style.width = 70;
absolutePositionElement.style.height = 70;
absolutePositionElement.style.backgroundColor = Color.black;
rootVisualElement.Add(absolutePositionElement);
}
注意,在EidtorWindow类里,有一个Property叫做public VisualElement rootVisualElement { get; }
,可以用于取得窗口的Visual Tree的root visual element。
Transformation between coordinate systems
VisualElement.layout.position和VisualElement.transform两个参数,决定了local coordinate system 和 the parent coordinate system直接的转换,静态类VisualElementExtensions为这些转换提供了一些方法:
Layout Engine可以基于Visual Elements的layout和style属性自动计算UI布局,它是基于Github上的开源项目Yoga开发的(Yoga implements a subset of Flexbox: a HTML/CSS layout system)。
要学习Yoga和Flexbox,还需要到文档上提供的链接里去看,这里就不挂链接了。
Layout System默认有以下特点:
使用layout engine的一些方法:
flex-grow: ;
) ,当element的大小由其兄弟element决定时, flexGrow 属性的值用作权重。UXML是一种文本文件,它定义了UI的逻辑结构,本章会介绍UXML的语法、还要如何写入、读取和定义UXML模板等,还包含了一些自定义新的UI Element的方法,以及使用UQuery的方法。
In UXML 可以:
如何理解USS和UXML文件
这里强调一下初次看到这的时候我不理解的问题,UI的structure和UI layout有何区别?
其实Structure代表了节点的组织关系,就是Hierarchy里的父子关系,而UI Layout则代表了每个UI节点的具体的style等参数,如下图所示,HTML文件记录是Structure,CSS文件里记录的是每个节点的绘制信息,这样一看应该就很清楚了:
类比到UI Toolkit里,UXML文件用于描述整体节点之间的Structure,也就是对应的父子连接关系,而每个节点都有自己的USS文件,用于描述那个节点的尺寸等UI信息。
自定义Visual Element
Unity的原文档连接在这里:https://docs.unity3d.com/2020.1/Documentation/Manual/UIE-UXML.html
坦白说,这一段文档官方文档居然没有配合具体的代码展示,感觉官方写的东西就是一坨屎,下面会基于这坨垃圾玩意儿,进行解释,然后加上自己的解释和样例去帮助理解。
// 需要继承于VisualElement
class StatusBar : VisualElement
{
// 必须要实现一个默认构造函数
public StatusBar()
{
}
public string status {
get; set; }
}
然后我试了试,创建了个EditorWindow窗口,代码如下:
public class MyEditorWindow :EditorWindow
{
[MenuItem("Window/Open My Window")]
public static void OpenWindow()
{
var window = GetWindow<MyEditorWindow>();
StatusBar statusBar = new StatusBar();
statusBar.status = "Hello World";
statusBar.style.width = 50;
statusBar.style.height = 50;
window.rootVisualElement.Add(statusBar);
}
}
然后打开EditorWindow,发现没有任何显示,但是我打开UIElements Debugger发现是有东西的,只是没有显示String和UI而已,如下图所示:
这是因为,还没有读取对应的UXML,来决定该element的结构。为了读取UXML文件,需要创建一个对应的factory类,这个类可以继承于UxmlFactory
,一般推荐在Element类内定义,代码如下:
class StatusBar : VisualElement
{
// 在定义了这个类之后, 就可以在UXML文件里写StatusBar元素了,
// 不过我还不熟悉这个new class的写法
public new class UxmlFactory : UxmlFactory<StatusBar> {
}
...
};
class StatusBar : VisualElement
{
public new class UxmlFactory : UxmlFactory<StatusBar, UxmlTraits> {
}
// 取的类名不变
public new class UxmlTraits : VisualElement.UxmlTraits
{
// 创建一个StringAttribute对象, StatusBar只有一个Attribute, 名字叫status
UxmlStringAttributeDescription m_Status = new UxmlStringAttributeDescription {
name = "status" };
// 定义UxmlChildElementDescription函数
// 函数返回空的IEnumerable,表示StatusBar的没有任何child element, 也不接受任何children
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get {
yield break; }
}
// 会从XML parser里读取到对应的bag, 然后赋值给m_status
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
{
// calls base.Init() to initialize the base class properties.
base.Init(ve, bag, cc);
// 把此类定义在StatusBar内部, 可以直接获取私有成员status
((StatusBar)ve).status = m_Status.GetValueFromBag(bag, cc);
}
}
public StatusBar()
{
m_Status = String.Empty;
}
string m_Status;
public string status {
get; set; }
}
UxmlTraits类有两个作用:
上面的Trait类里定义了UxmlStringAttributeDescription
对象代表String的Attribute,一共有以下类型:
前面的uxmlChildElementsDescription函数里,写的代码是不支持任何Children的,如果想支持任何Children,可以这么写:
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
{
get
{
yield return new UxmlChildElementDescription(typeof(VisualElement));
}
}
UxmlFactory和UxmlTraits实例
这一块内容Unity的文档居然没有给例子,真是辣鸡,这里举个例子。
举个例子,在定义这么一个类以后:
class TwoPaneSplitView : VisualElement
{
// 定义UxmlFactory类, 用于在UXML里识别此类, 并在里面创建此类对应的Tag
public new class UxmlFactory : UxmlFactory<TwoPaneSplitView, UxmlTraits> {
}
// UxmlTraits类用于在UXML文件里添加自定义的Attributes, 它们都可以在UI Builder里看到
public new class UxmlTraits : VisualElement.UxmlTraits{
}
}
只有在里面加上了UxmlFactory,才可以在Uxml里这么写:
<BuilderAttributesTestElement/>// 目前没有加任何Attribute
Defining a namespace prefix
在完成上面的代码后,就可以在UXML文件里使用对应的Element了,如果是在Namespace里面自定义Element,还需要做额外的处理。
需要定义一个namspace prefix, Namespace prefixes其实就是在UXML的root element上面声明的attributes,它会replace the full namespace name when scoping elements.
写法如下:
// This can be done at the root level (outside any namespace) of any C# file of the assembly.
[assembly: UxmlNamespacePrefix("My.First.Namespace", "first")]
[assembly: UxmlNamespacePrefix("My.Second.Namespace", "second")]
schema generation系统会做这些事情:
这个element上添加namespace prefix的定义xsi:schemaLocation
attribute.接下来,需要更新项目里的UXML schema,选择Assets > Update UXML Schema,保证text editor可以辨别出来新的element。
The defined prefix is available in the newly created UXML by selecting Create > UI Toolkit > Editor Window in the Project/Assets/Editor folder.
Customizing a UXML name
可以通过override继承于UxmlFactory类的Property,代码如下:
public class FactoryWithCustomName : UxmlFactory<..., ...>
{
// 暂时还不知道具体会展示在哪里
public override string uxmlName
{
get {
return "UniqueName"; }
}
public override string uxmlQualifiedName
{
get {
return uxmlNamespace + "." + uxmlName; }
}
}
Selecting a factory for an element
默认情况下,IUxmlFactory
会创建一个element,然后选择根据它的名字来选择对应的element,主要是为了让它在UXML文件里能够被识别出来
其实就是用XML语言写的表示UI逻辑结构的uxml文件,举个例子:
<-- 第一行是XML declaration, it is optional, 只可以出现在第一行, 前面不允许有空格-->
<-- version的attribute必须要写, encoding可以不写, 如果写了, 就必须说清楚文件的字符encoding -->
<-- UXML 代表document root, 包含了用于namespace prefix definitions和schema的源文件位置的attributes -->
<UXML
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<-- 下面这句话有点像是using UnityEngine.UIElements, 表示后面的Label什么的都是这个ns下的, 这里的ns是作为默认的ns -->
xmlns="UnityEngine.UIElements"
xsi:noNamespaceSchemaLocation="../UIElementsSchema/UIElements.xsd"
xsi:schemaLocation="UnityEngine.UIElements ../UIElementsSchema/UnityEngine.UIElements.xsd">
<-- 这下面的Label、Box、Button等都是Visual Element -->
<-- 前面的Label代表继承于VisualElement的类名, 而后面的text叫做Element的Attributes--->
<Label text="Select something to remove from your suitcase:"/>
<Box>
<Toggle name="boots" label="Boots" value="false" />
<Toggle name="helmet" label="Helmet" value="false" />
<Toggle name="cloak" label="Cloak of invisibility" value="false"/>
Box>
<Box>
<Button name="cancel" text="Cancel" />
<Button name="ok" text="OK" />
Box>
UXML>
补充几点:
xmlns:engine="UnityEngine.UIElements"
,这种写法,相当于是typedef,之后可以写
,等同于
的tag里包含对应的 namespace definition and schema file location,同时还要包含Unity原本的namespacesVisualElement通用的Attribute
一共有如下:
创建UXML template asset
When you create a new UXML template asset by selecting Asset > Create > UI Toolkit > Editor Window, the Editor automatically defines namespaces for you.
Adding styles to UXML
UXML文件可以引用USS文件,需要在任何element的声明下面使用这个element,举个例子:
<engine:UXML ...>
<engine:VisualElement class="root">
<-- 意思所有的VisualElement都在调用这个style.uss作为布局? -->
<Style src="styles.uss" />
engine:VisualElement>
engine:UXML>
此时的USS文化和UXML需要在相同文件夹下,具体的style.uss文件内容如下:
#root {
width: 200px;
height: 200px;
background-color: red;
}
也可以不要uss文件,直接UXML里一行代码设置style:
<engine:UXML ...>
<engine:VisualElement style="width: 200px; height: 200px; background-color: red;" />
engine:UXML>
Reusing UXML files
UXML文件也可以作为类似prefab的东西进行复用,举个例子,这里有个当作人像的UXML文件,它的UI里有一个图形和人名:
<engine:UXML ...>
<engine:VisualElement class="portrait">
<engine:Image name="portaitImage" style="--unity-image: url(\"a.png\")"/>
<engine:Label name="nameLabel" text="Name"/>
<engine:Label name="levelLabel" text="42"/>
engine:VisualElement>
engine:UXML>
在其他的UXML文件里,就可以把这个人像的UXML作为模板使用了:
<engine:UXML ...>
<-- 类名叫Template, 路径src为...., Element的名字为Portrait, 感觉这里是创建了一个模板的类 -->
<engine:Template src="/Assets/Portrait.uxml" name="Portrait"/>
<engine:VisualElement name="players">
<-- Instance代表模板的示例, 后面template后面是类名, 然后根据name创建具体的Instance -->
<engine:Instance template="Portrait" name="player1"/>
<engine:Instance template="Portrait" name="player2"/>
engine:VisualElement>
engine:UXML>
总结来说,就是使用Template
和Instance
关键字,可以在UXML里使用别的UXML里创建的class
Overriding UXML attributes
即使基于UXML Template创建了Instance,还是可以override其elements里默认的Attribute的值。
具体操作如下,要写一行xml语句指名下面的内容:
举个例子,看下面这段代码:
<-- 由于override的是Instance不是Template, 所以可以输入多个参数,比如这里输入
两个参数:一个是类名,一个是Element的名字,满足这两个条件的Element, 其text的attribute都会被Override -->
<AttributeOverrides element-name="player-name-label" text="Alice" />
再举一个例子,假设有不同的玩家,他们都要展示相同的Template,但是每个人具体的数值不同:
<-- 指明namespace -->
<UXML xmlns="UnityEngine.UIElements">
<-- 其实是UnityEngine.UIElements.Label -->
<-- 创建两个Label, 名字分别为player-name-label和player-score-label -->
<Label name="player-name-label" text="default name" />
<Label name="player-score-label" text="default score" />
UXML>
在创建完模板后,可以创建其Instance,然后override它的attributes,其实就是语法上的学习,没什么难度:
<-- 添加两个namespace的include -->
<UXML xmlns="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
<-- 声明使用的模板和路径 -->
<Template src="MyTemplate.uxml" name="MyTemplate" />
<-- 基于名为MyTemplate模板创建Instance -->
<Instance name="player1" template="MyTemplate">
<-- Override两个element的text对应的attribute -->
<AttributeOverrides element-name="player-name-label" text="Alice" />
<AttributeOverrides element-name="player-score-label" text="2" />
Instance>
<Instance name="player2" template="MyTemplate">
<AttributeOverrides element-name="player-name-label" text="Bob" />
<AttributeOverrides element-name="player-score-label" text="1" />
Instance>
UXML>
Overriding multiple attributes
上面的例子都只override了一个attribute,用同样的方法还可以ovverride多个attribute:
<-- ovverride text和tooltip两个attribute -->
<AttributeOverrides element-name="player-name-label" text="Alice" tooltip="Tooltip 1" />
Nesting attribute overrides
When you override attributes in nested templates, the deepest override takes precedence.
UXML文件可以引用别的UXML文件和USS文件
其中,和
Style
两种Element可以接受src
或者path
的attribute,二者有些许差别。
src
存的是相对路径,要么是相对于Project Root路径,要么是相对于所在的UXML文件的路径。举个例子,我的UXML文件在Assets\Editor\UXML下,USS文件在Assets\Editor\USS下:
src="../USS/styles.uss"
,如果要读取别的UXML文件,那么src="template.uxml"
src="/Assets/Editor/USS/styles.uss"
or src="project:/Assets/Editor/UXML/template.uxml"
.path
path只支持在Resources或者Editor的Resouces下的文件夹的文件:
path="template"
代表Assets/Resources/template.uxml
。path="template.uxml"
代表Assets/Editor Default Resources/template.uxml.
很简单,记录下写法:
// 写法一
var template = EditorGUIUtility.Load("path/to/file.uxml") as VisualTreeAsset;
// 这里的parentElement, 可以是EditorWindow下的rootVisualElement
template.CloneTree(parentElement, slots);
// 写法二
var template = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("path/to/file.uxml");
template.CloneTree(parentElement, slots);
实际使用的时候大概是这样:
public class MyWindow : EditorWindow {
[MenuItem ("Window/My Window")]
public static void ShowWindow () {
EditorWindow w = EditorWindow.GetWindow(typeof(MyWindow));
VisualTreeAsset uiAsset = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/MyWindow.uxml");
VisualElement ui = uiAsset.CloneTree(null);
w.rootVisualElement.Add(ui);
}
void OnGUI () {
// Nothing to do here, unless you need to also handle IMGUI stuff.
}
}
UQuery
UQuery是Unity实现的自己版本的JQuery/Linq,可以使用UQuery获取VisualElement的子节点Tree里特定的Element,示例代码如下:
// 查找第一个叫foo的Button
root.Query<Button>("foo").First();
// 对每个叫foo的Button做...
root.Query("foo").Children<Button>().ForEach(//do stuff);
总结了UnityEngine.UIElements和UnityEditor.UIElements命名空间下可以用 的UXML Element:
基本的Element
就两种:
public class BindableElement : VisualElement, IBindable
Utilities
提供的常用的UI Element有:
focus-index
和focusable
两个Attribute这些Element都是在UnityEngine.UIElements下
Templates
一共三种:
太多了,自己看吧。。。。
https://docs.unity3d.com/2021.2/Documentation/Manual/UIE-ElementRef.html
每个Visual Element都有一个style属性,可以使用USS文件来定义它的UI,规则如下:
Style Rule
我理解的就是语法规则,如下所示:
selector {
property1:value;
property2:value;
}
Attaching USS to visual elements
AssetDatabase.Load()
或Resources.Load()
加载文件,使用VisualElement.styleSheets.Add()
添加stylesheetUSS Selector负责根据uss文件里的内容名字,找到对应匹配的Style Rule,在我理解,Selector本质就是一些语法,通过不同的语法,可以实现uss里的Style Rule能应用到指定的Visual Element上
常见的写法:
#name{}
Button{}
.classlist{}
// 删除一整个数组的UI Element
for (int i = 0; i < modelAreasUI.Count; i++)
{
modelAreasUI[i].parent.Remove(modelAreasUI[i]);
}
modelAreasUI.Clear();
XML的Element可以拥有Attribute,二者是从属关系,比如下面的
<person gender="female">
里的person是Element,而gender是Attribute
再看两个例子:
<person gender="female">
<firstname>Annafirstname>
<lastname>Smithlastname>
person>
<person>
<gender>femalegender>
<firstname>Annafirstname>
<lastname>Smithlastname>
person>
第一个例子里,gender是Attribute,第二个例子里,gender是element
参考来源:https://www.w3schools.com/xml/schema_intro.asp
https://www.differencebetween.com/difference-between-xml-and-vs-xsd/
schema翻译过来是模式、概要和议程。在计算机术语里,schema经常用于描述不同类型的数据的structure,最通用的就是数据和XML的schemas。
An XML Schema describes the structure of an XML document. The XML Schema language is also referred to as XML Schema Definition (XSD). 如下所示是一个XSD的例子:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
<xs:element name="from" type="xs:string"/>
<xs:element name="heading" type="xs:string"/>
<xs:element name="body" type="xs:string"/>
xs:sequence>
xs:complexType>
xs:element>
xs:schema>
核心在于,xml schema旨在定义XML文档本身的结构和内容,xml和xml schema的区别,也可以认为是XML和XSD的区别。在我理解,比如说xml里的node的节点关系,element可以添加attribute这些,应该都是schema来设置的。
参考链接:https://css-tricks.com/snippets/css/a-guide-to-flexbox/
在VisualElement里有一个Property:
StyleFloat flexGrow: Specifies how much the item will grow relative to the rest of the flexible items inside the same container.
本质上flexGrow是一个float值,这个概念源于Flexbox Layout
,用于为那些尺寸不确定、或者说是动态的Box进行布局的分配,其核心在于,在一个固定尺寸的Container里,如何灵活的变化里面的Box的尺寸,让他们能布局在Container里
The Flexbox Layout (Flexible Box) module (a W3C Candidate Recommendation as of October 2017) aims at providing a more efficient way to lay out, align and distribute space among items in a container, even when their size is unknown and/or dynamic (thus the word “flex”).
flex-direction
属性uss或者说css相关的layout的代码,根据作用的对象,可以分为两种,由于Visual Element,往往是Parent作为所有Children的容器,所以这里分为:
display
如下所示,可以定义一个允许子节点灵活变化的容器:
/* 可以选择flex或者inline-flex */
.container {
display: flex; /* or inline-flex */
}
flex-direction
决定了main-axis的方向,也就是容器里的元素排列的方向,一共四种:左到右、右到左、上到下、下到上
.container {
flex-direction: row | row-reverse | column | column-reverse;
}
flex-wrap
正常情况下,flex container里的flex items会尽量放到一行(或一列),这里可以通过flex-wrap设置,允许它在需要的时候放到多行
.container {
flex-wrap: nowrap | wrap | wrap-reverse;
}
flex-flow
它是flex-direction和flex-wrap的总体简称,默认的就是row nowrap:
/* main axis沿竖直方向, 而且有wrap */
.container {
flex-flow: column wrap;
}
justify-content
This defines the alignment along the main axis. 还有一些定义,可以定义main axis上的flex items对齐的一些方法,如下图所示:
代码如下:
.container {
justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
}
align-items
This defines the default behavior for how flex items are laid out along the cross axis on the current line. 前面决定的是flex items沿着main axis的对齐,这里指的是flex items沿着cross axis的对齐,如下图所示,main-aixs是横向的,cross axis是纵向的:
写法如下:
.container {
align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
}
align-content
感觉跟align-items很像,如下图所示:
代码如下:
.container {
align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
}
前面提到的flex属性都是针对flex container的,用于调整里面的元素的layout,下面介绍用于container里面具体的item的property
order
flex item有个属性叫order,用于确定其排序,如下图所示:
.item {
order: 5; /* default is 0 */
}
flex-grow
This defines the ability for a flex item to grow if necessary. 其实就是在它所有的兄弟里面,它试图占有的权重值,如下图所示,权重为2的,长度也是2倍,如果所有的flex item的flex-grow都是1,那么他们的长度还会是一样的:
.item {
flex-grow: 4; /* default 0 */
}
flex-shrink
如果有必要的话,一个flex item会收缩
.item {
flex-shrink: 3; /* default 1 */
}
flex-basis
代表元素被分配尺寸之前的默认尺寸,代码如下:
.item {
flex-basis: | auto; /* default auto */
}
flex
flex-grow(子节点扩大权重)、flex-shrink(允许收缩的程度)和flex-basis(基本默认尺寸)这三个属性的总体简称,代码如下:
/*It is recommended that you use this shorthand property rather than set the individual properties. The shorthand sets the other values intelligently.*/
.item {
flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}
align-self
自定义一个元素的alignment:
.item {
align-self: auto | flex-start | flex-end | center | baseline | stretch;
}
// 获得Visual Element的实际尺寸
element.resolvedStyle.width //(recommended)
element.resolvedStyle.height //(recommennded)
element.worldBound //(relative to the EditorWindow)
element.transform
element.layout
但是要注意一点,这些参数都不会在第一帧创建对应的element之后马上生效,而是需要等待Unity计算每个元素的size和position之后,才可以生效。
如果想要在该值可用后的第一时间读取该值,可以在该元素上登记GeometryChangeEvent回调函数
在代码里看到了这个类,主要是API方面,类的定义如下:
// 此类的实例表示一个Visual Element的Tree, 这个Tree是从UXML文件里读取出来的
// 在UXML文件里, 每一个Node(xml概念里的Node)都代表一个VisualElementAsset
public class VisualTreeAsset : ScriptableObject
{
public VisualTreeAsset();
...
}
其实这个类就是帮助从UXML文件里,得到对应的Visual Element的,代码如下所示:
VisualTreeAsset template = EditorGUIUtility.Load("Assets/TrainningDataViewer.uxml") as VisualTreeAsset;
VisualElement root = template.CloneTree();
如下图所示,在UnityC#的源码里去引用得到的:
分为两种,一类是在UnityEditor下用到,这里提到的Inserter、SelectionDropper、ShortcutHandler和ContentZoomer都是在GraphView的Namespace里提供的,而MouseManipulator是Unity UI Elements命名空间下的。
继承MouseManipulator的有:
其中,ElementResizer、ClickSelector、ContentDragger、Dragger、EdgeConnector、EdgeManipulator、FreehandSelector和RectangleSelector都是在GraphView的命名空间下的
其实在UI Samples里都有介绍,代码如下:
// 在uxml里加入Enum Field(也可以在代码里加入)
<uie:EnumField label="MyEnum" value="2D" name="MyEnum"/>
// 在C#脚本里
enum MyEnum
{
One,
Two
}
var enumField = rootVisualElement.Q<EnumField>("MyEnum");
enumField .Init(MyEnum.One);// 初始值
enumField .value = MyEnum.Two;// 再设别的值
参考链接:https://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEditor.UIElements.PopupField-1.html
构造函数的接口:
public PopupField(string label, List<T> choices, T defaultValue, Func<T, string> formatSelectedValueCallback = null, Func<T, string> formatListItemCallback = null)
实际使用:
List<string> s = new List<string>();
s.Add("321");
s.Add("11");
var ClipsField = new PopupField<string>("Choose Clips", s, "11");
Add(ClipsField);// 加到一个Visual Element里
效果如下图所示,跟EnumField有点像:
可以通过下面的方式直接进行选择:
// 相当于点选第21个choice
ClipsField.index = 20;