从锚点来谈 Rect Transform 组件

前言

手游开发过程中,由于各种不同分辨率手机的存在,为此 UI 相对位置布局是必不可少的一步。在使用 Unity UI 开发界面时,要实现一个 UI 元素的位置相对父元素不发生变化,脑海中能想到的可能有以下几种实现方式:

  • 编写脚本,根据父元素的位置、大小等信息来动态设定当前元素的位置,使其能够保证位置相对父元素的位置不变化

  • 使用 Rect Transform 组件的 Anchors 属性设置,保证相对位置布局

  • ...

接下来我们就来看看这个 Anchors。

Anchors

Anchors 由两个归一化的 Vector 组成 - Min 和 Max,这两个向量的值都是在父节点中并且归一化的值。两个向量会组成一个矩形,矩形的四个顶点就是四个 Anchor(锚点),这两个向量对应了 RectTransform 类中的 anchorMinanchorMax 属性。Unity 中,Anchors 是可以手动编辑的,如下:

上图中 Rect Transform 下的 Anchors Min 和 Max 就是锚点构成的矩形左下角和右上角的坐标,当这些坐标设置为不同的值时,其所在的 UI 元素的位置和尺寸大小将由不同的属性来控制。

  1. 默认情况下,Min 和 Max 的值都为 (0.5, 0.5),这时构成的锚点矩形就是一个点(四个锚点发生重合),位于父节点 UI 元素的正中间。此时当前 UI 元素的位置由 Pos X 和 Pos Y 表示,这两个属性是相对 Anchor 的位置,即 RectTransform 类中的 anchoredPosition 属性。如果 Anchor 四个锚点分散开,那么就是相对 Anchors 构成矩形的中心点的位置。尺寸大小由 Width 和 Height 表示,如下图:

上图测试 UI 元素的 Pivot (支点/中心点)默认为 (0.5, 0.5)。定位、缩放和旋转都会根据 Pivot 的值来执行。

改变父节点 UI 元素的大小尺寸不会改变当前 UI 元素的大小尺寸且 UI 元素的位置永远位于父节点 UI 元素的正中间,如下:

四个锚点重合然后设置 Pos X 和 Pos Y,是常用的一种相对布局的方式。例如在 Canvas 中,有一个按钮需要始终位于 Canvas 的右上角某个位置,由于Canvas 会在不同的分辨率下自适应大小,因此此时只需要设置四个锚点都位于 Canvas 的右上角,即 Min 为 (1, 1),Max 也设置为 (1, 1),然后设置按钮的 Pos X 和 Pos Y 就是按钮相对 Canvas 右上角的距离长度。如下所示:

改变 Canvas 的分辨率,观察 UI 元素相对 Canvas 的位置不会发生变化。如下图:

Unity 中默认提供了多种实现相对布局设置锚点的快捷方式:

  1. 接着讲 Min 设置为 (0, 0),Max 设置为 (1, 1),其实可以看出 UI 元素的位置以及尺寸大小由 Left、Top、Pos Z、Right 和 Bottom 共同控制。如下图:

改变 Left、Top、Right 和 Bottom 中的这些值,UI 元素的位置或尺寸大小都有可能发生变化,如下所示:

可以看出,此时的四个锚点分别位于父节点元素的四个顶点。

  • Left 决定了 UI 元素所在矩形的左边(不是 Pivot 中心点)距离锚点构成矩形左边的长度

  • Top 决定了 UI 元素所在矩形的上边距离锚点构成矩形上边的长度

  • Right 决定了 UI 元素所在矩形的右边距离锚点构成矩形右边的长度

  • Bottom 决定了 UI 元素所在矩形的下边距离锚点构成矩形下边的长度

改变上面这些值,会改变当前 UI 元素的尺寸大小,改变父节点 UI 元素的大小也会改变当前 UI 元素的尺寸大小(因为锚点矩形决定的相对距离值),如下:

  1. 下面继续进行试验,将 Min 设置为 (0, 1),Max 设置为 (1, 1),此时锚点构成的矩形是一条直线,位于父节点元素所在矩形的上边。如下所示:

此时,位置和大小由以下属性决定:

  • Left 决定了当前 UI 元素所在矩形的左边距离锚点构成矩形(这里是一条直线,即最左边的那个点的 X 坐标)的长度

  • Pos Y 决定了当前 UI 元素的 Y 坐标,它是相对于锚点的 Y 坐标的(即 Y 坐标所在的值是坐标系的零值)

  • Right 决定了当前 UI 元素所在矩形的右边距离锚点构成矩形(这里是一条直线,即最右边的那个点的 X 坐标)的长度

  • Height 就是 UI 元素的高度,父节点 UI 元素高度改变也不会改变当前 UI 元素的高度。因为高度有这个值决定

根据类似的设置,也可以让锚点组成的矩形(直线)分别位于左边、右边或下边,这样能做到某一个方向尺寸大小固定,位置相对布局,另一个方向尺寸大小跟随父元素变化,相对父元素距离不发生变化。

Rect Transform 组件

分析完了最常用了 Anchors,下面来到锚点的载体 Rect Transform 组件,它在 Unity 编辑器呈现如下图:

那么什么是 Rect Transform?它的作用又是什么?首先来看看官方文档的定义:

The Rect Transform component is the 2D layout counterpart of the Transform component. Where Transform represents a single point, Rect Transform represent a rectangle that a UI element can be placed inside. If the parent of a Rect Transform is also a Rect Transform, the child Rect Transform can also specify how it should be positioned and sized relative to the parent rectangle.

RectTransform 组件是 2D UI 中常用的一个组件,它代表了 UI 元素构成布局的一个矩形区域;在 RectTransform 组件中存储了 position、size 和 anchoring 等信息(比如上面讲过的 Anchors 和后面将要讲到的其它属性),这些信息将指定一个 UI 元素的的位置、尺寸大小和如何缩放等;另外 RectTransform 继承自 Transform 类,因此它也拥有 Transform 类中诸如 positionlocalPosition 等属性。

接着再来看看 Rect Transform 组件的其它属性。

RectTransform 中的其它属性

首先,在 Unity 编辑面板上可以编辑的属性有以下这些:

  • Pos (X, Y, Z) - 在前边分析 Anchors 的时候也提到过 Pos X 和 Pos Y,当四个锚点重合这两个属性就是当前 UI 元素的支点(中心点)相对重合锚点的位置(x 和 y 坐标);如果 Anchor 四个锚点分散开,那么就是 UI 元素的支点(中心点)相对 Anchors 构成矩形的中心点的位置;Pos X 和 Pos Y 对应了 RectTransform 类中的 anchoredPosition 成员变量。Pos Z 在 Canvas 的 Render Mode 设置为不同模式时,有不同的意义: 当 Render Mode 为 Screen Space - OverlayScreen Space - Camera (不设置 Camera)时,表示当前 UI 元素相对于父元素的 Z 偏移量(一个 UI 单位和世界空间下单位之间的转换与当前 RectTransform 祖先元素的 Scale 相关);当为 Screen Space - Camera 并设置 Camera 时,Pos Z 等于 UI 元素的 localPosition.z,真正的 World Position 的 z 坐标和其祖先元素的 Scale、Canvas 的 Plane Distance 以及相机的 FOV等信息相关;当设置为为 World Space 时,表示当前 UI 元素相对于父元素的 Z 偏移量(UI 单位和世界空间下单位之间的转换与当前 RectTransform 祖先元素的 Scale 相关,因此可以根据各个祖先的这个值计算出当前 UI 元素世界空间下 Z 方向的坐标)。

  • Width/Height - 当前 UI 元素所在矩形的宽和高,对应了 RectTransform 类中的 rect 中的 m_Width 和 m_Height。

  • Left、Top、Right 和 Bottom - 当前 UI 元素所在矩形的四条边相对于锚点构成的矩形四条边的距离长度。当 Anchors 设置为拉伸(四个锚点不全部重合)的时候,在 Unity 编辑器界面可以设置不同的属性。

  • Anchors - 锚点属性上面我们详细分析过,这里再次提一下。它包含 Min 和 Max 两个向量,分别代表锚点在父节点 UI 元素中构成的矩形的左下角和右上角的归一化坐标。

    • Min - 锚点构成的矩形左下角(相对父节点尺寸)的归一化坐标,(0,0) 代表父节点左下角,(1,1) 代表父节点右上角;对应了 RectTransform 类中的 anchorMin 成员变量。

    • Max - 锚点构成的矩形右上角(相对父节点尺寸)的归一化坐标,(0,0) 代表父节点左下角,(1,1) 代表父节点右上角;对应了 RectTransform 类中的 anchorMax 成员变量。

  • Pivot - 当前 UI 元素的矩形支点(中心点),这个值是相对自身而言的,并且归一化;元素旋转、缩放都围绕着这个支点进行;对应了 RectTransform 类中的 pivot 变量。

  • Rotation - 围绕 X、Y 和 Z 三个轴的旋转角度(单位: 度)。

  • Scale - X、Y 和 Z 三个维度上的缩放系数。

上面的这些属性计算是在一帧的末尾进行的(计算顶点数据之前),这样能够保证计算这些数据的时候所有的参数都是最新的。因此在第一次回调 StartUpdate 方法的时候这些数据还未被计算好;在这种情况下若需要使用这些属性,可以调用 Canvas.ForceUpdateCanvases() 方法强制更新这些数据。

在 RectTransform 类中还有一些其它属性,如下:

  • position - 在世界空间下的位置

  • localPosition - 在父节点空间下的位置,父节点坐标系以其支点(中心点) Pivot 为原点(单位是像素)

  • anchoredPosition - 支点(中心点)相对锚点构成矩形中心点的位置

  • anchoredPosition3D - 这个属性和 anchoredPosition 有点类似,代表了当前 UI 元素矩形支点相对 Anchors 的 3D 位置,其值返回是 (anchoredPosition.x, anchoredPosition.y, localPosition.z)

  • offsetMax - 元素矩形右上角相对锚点构成矩形右上角的位移

  • offsetMin - 元素矩形左下角相对锚点构成矩形左下角的位移

  • rect - The calculated rectangle in the local space of the Transform.

    • x - 当前 UI 元素所在矩形的左下角 X 坐标(相对自身支点(中心点) Pivot)

    • y - 当前 UI 元素所在矩形的左下角 Y 坐标(相对自身支点(中心点) Pivot)

    • width - 当前 UI 元素所在矩形的宽度

    • height - 当前 UI 元素所在矩形的高度

    • center - 当前 UI 元素所在矩形的中心点位置(相对自身支点(中心点) Pivot)

    • xMin - 当前 UI 元素所在矩形的最小 X 坐标(相对自身支点(中心点) Pivot)

    • ... (更多属性请参考文档)

  • sizeDelta - 当锚点不重合时,这个属性就是当前 UI 元素所在的矩形的宽高与 Anchors 构成的矩形的宽高的差值;当四个锚点重合时,这个值就是 RectTransform 的宽和高

RectTransform 源码分析

讲过了大部分 RectTransform 类的成员属性变量,最后我们再来看看 RectTransform 部分源码。

GetLocalCorners 方法

这个方法很简单,就是根据当前 RectTransform 的 rect 内部的 x、y、xMax 和 yMax 等数据返回本地坐标系中当前 UI 元素所在矩形四个角的位置(从左小角开始依次顺时针返回)。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void GetLocalCorners(Vector3[] fourCornersArray)
{
// other code ...
Rect rect = this.rect;
float x = rect.x;
float y = rect.y;
float xMax = rect.xMax;
float yMax = rect.yMax;
fourCornersArray[0] = new Vector3(x, y, 0.0f);
fourCornersArray[1] = new Vector3(x, yMax, 0.0f);
fourCornersArray[2] = new Vector3(xMax, yMax, 0.0f);
fourCornersArray[3] = new Vector3(xMax, y, 0.0f);
}

GetWorldCorners 方法

GetLocalCorners 相对应,返回世界空间下当前 UI 元素所在矩形四个角的位置,代码如下:

1
2
3
4
5
6
7
8
9
public void GetWorldCorners(Vector3[] fourCornersArray)
{
// other code ...
this.GetLocalCorners(fourCornersArray);
Transform transform = this.transform;
for (int index = 0; index < 4; ++index)
fourCornersArray[index] = transform.TransformPoint(fourCornersArray[index]);
}

代码也很简单,获取本地空间下的四个角的坐标,然后调用 Transform.TransformPoint 方法将本地空间下的这四个角的位置转换到世界空间下并返回。

GetRectInParentSpace 方法

获取父节点空间下当前 RectTransform 的 rect 的表示。

SetInsetAndSizeFromParentEdge 方法

根据指定边设置当前 UI 元素的相对父元素矩形的距离,同时设置 UI 元素尺寸大小;调用该方法后,当前 RectTransform 的 anchorMin、anchorMax、sizeDelta 和 anchoredPosition 等属性都会被修改。

SetSizeWithCurrentAnchors 方法

设定指定轴上 RectTransform 的尺寸大小。代码如下:

1
2
3
4
5
6
7
public void SetSizeWithCurrentAnchors(RectTransform.Axis axis, float size)
{
int index = (int) axis;
Vector2 sizeDelta = this.sizeDelta;
sizeDelta[index] = size - this.GetParentSize()[index] * (this.anchorMax[index] - this.anchorMin[index]);
this.sizeDelta = sizeDelta;
}

计算过程也很简单。之前说过在锚点不重合的时候 sizeDelta 就是当前 UI 元素所在的矩形的宽高与 Anchors 构成的矩形的宽高的差值,上面的计算过程就是计算这个差值的过程,根据指定的 size 减去锚点构成矩形的 size 得到 sizeDelta,最后保存到当前 UI 元素的 RectTransform 的 sizeDelta 中。

ForceUpdateRectTransforms 方法

这个方法用来强制重新计算内部的数据。之前提到过 Canvas.ForceUpdateCanvases() 方法也可以完成这样的操作,当 RectTransform 数据发生变化,其会被注册到 CanvasUpdateRegistry 中,当 Canvas.ForceUpdateCanvases() 被调用,CanvasUpdateRegistry 会执行 RectTransform 的重新构建操作从而更新 UI。

参考