Contents

02布局控制相关

布局控制

主要记录布局控制相关一些控件的效果。主要是每个参数所表示的含义,以及各种边界情况下的效果。

其实一般来讲,我不太喜欢用官方提供的布局控制器。因为很多动态效果无法提供。 对于官方的布局控制器,可以认为做了两件事情,一:计算对应布局概念所需要的RectTransform数据,二:修改子对象的RectTransform。相当于执行了计算并设置的步骤。这导致很多动态效果很难去实现,如果使用布局控制器的情况下,那么动画只能通过在被布局子对象中嵌套一层节点,即在子对象中去模拟动画效果来实现。这样计算动画效果一方面很不直接,另一方面是对于很复杂的动画,实现起来就更加困难了。

但是其也有对应的好处,一:其已经实现了一套布局结构。二:这套布局结构可以在Editor模式下预览效果。

本文主要记录一下这些官方布局器跟各种组件的组合效果。

布局元素

这里所说的布局元素。并不是对应RectTransform控件上的各种参数值。可以认为是控件上一套参数,定义该控件潜在需要的布局大小,或者说最后RectTransform算出来的Rect大小。为了方便说明这个属性概念是存在的,可以先来看ContentSizeFitter组件。

ContentSizeFitter

该组件图如下:

其只有两个参数

  • HorizontalFit:根据所在控件的布局元素来设置RectTransform的水平宽度。
  • VerticalFit:根据所在控件的布局元素来设置RectTransform的垂直高度。

可以看到所在GameObject的RectTransform中width与height是锁死的。这是因为ContentSizeFitter根据了所在GameObject的Image的布局元素来进行设置。所以这也导致其为一个自适应组件——根据变化的子对象布局元素来设置自己的RectTransform。

而这就涉及到有哪些组件有布局元素属性。根据官方文档来看,主要有四类:

  • Image
  • Text
  • LaytouGroup即自动布局组件
  • LayoutElement即下面要说明的LayoutElement组件。

LayoutElement

该组件用来覆写当前控件的布局元素属性。为了方便说明还是将布局元素概念与该组件LayoutElement区分开来。

看到其提供了6个数据字段,简单来看即

  • Width的Min/Preferred/Flexible
  • Height的Min/Preferred/Flexible 以及
  • IgnoreLayout:勾选使得系统忽略该布局控件。
  • LayoutPriority:布局优先级,数值大的组件的值会生效。忽略数值小的组件。

而6个数据字段则定义了对应覆写宽高的三类属性,对于ContentSizeFitter组件来说,当:

  • Fit设置为MinSize时,会使用Min开头的对应值来设置RectTransform。
  • Fit设置为PreferredSize时,会使用Preferred开头的对应值来设置RectTransform。

而这三个类型的值,则会在自动布局器中有对应语义用途。

根据官方文档,任何带有RectTransform的GameObject都可以视为一个LayoutElement,但是其对应Min/Preferred/Flexible都默认为0。添加某些Component后可以改变这些布局元素的对应值。

其中Image和Text Component都是可以提供对应布局元素值的组件。添加该组件后,其会修改Preferred值为对应的图片大小,或者文本区域大小。

HorizontalLayoutGroup与VerticalLayoutGroup

即垂直布局器与水平布局。两个组件空座方向类似,只是方向不一样。只有看其中一个即可,组件参数如下:

其中

  • Padding:相当于定义子对象的布局范围相对于当前区域Rect的上下左右距离。
  • Spacing:则相当于定义每个子对象之间的间隔。
  • ChildAlignment:则说明了子对象开始布局的位置,有九个位置。即对应从布局区域九宫格位置开始布局。
  • ReverseArrangement:对于布局来说,有个方向匹配的问题。正常情况下,对于子对象列表来说在上面的会排在前面。勾选该选项可以使得排序反过来,在下面的会排在前面。但是布局所占区域不会改边。

布局效果如下图:

如果没有勾选任何ControlChildSize/UseChildScale/ChildForceExpand等参数。那么布局器只会根据子对象的RectTransform和上面的Padding,Spacing等参数来计算Postion进行布局。

现在来看这三个控制参数的语义:

ControlChildSize

布局控制器同时控制子控件的大小。可以看到这个效果目标跟ContentSizeFitter非常类似,只是从控制自己变成了控制子对象。改功能,也会根据子对象的布局元素来设置子对象的大小。同时会根据子对象的Min/Preferred/Flexible属性来设置子对象大小。根据官方文档计算流程如下:

  • 首先考虑MinWidth
    • 先计算所有子对象minimum width和spacing之和,称之为布局器的最小宽度(minimum width of the Horizontal Layout Group)。如果该最小宽度大于当前布局控件的宽度,则不再计算,并且将所有子对象宽度设置为最小宽度。
  • 如果最小宽度小于当前布局宽度,则考虑PreferredWidth。
    • 如果没有PreferredWidth,则直接考虑FlexibleWidth值的计算。
    • 如果有PreferredWidth。会开始计算preferred width和spaceing之和。称之为布局器的最适宽度(preferred width of the Horizontal Layout Group)。如果该最适宽度大于当前布局控件的宽度,则会按照一个比例规则开始进行分配。
  • 如果最适宽度小于当前布局宽度,则考虑FlexibleWidth值计算。
    • 如果没有FlexibleWidth的值,则直接会按照之前计算出的值来进行设置。
    • 如果有FelxibleWidth,则会按照FlexibleWidth值得比例,将剩余空间按比例进行分配。

简单来说可以认为这是一个递进考虑计算的过程。但是不同控制也会有不同效果。

PreferredWidth计算

先详细看一下PreferredWidth超出时候的计算效果。

我们先看一个例子,PreferredWidth计算示例:

父对象总宽度为400,设置如下表

对象编号 MinWidth PreferredWidth 实际值
1 100 250 175
2 100 150 125
3 50 150 100

可以看到实际分配值不是根据PreferredWidth比例来进行设置。实际是如下一个计算过程。

  • 先用当前的布局总宽度减去布局器的最小宽度,得到一个残余宽度值,设为$L_{r}$。
  • 对于每一个子对象使用PreferredWidth减去MinWidth,得到一个PreferredWidth的分配量,设为$W_{i}=W_{preferred}-W_{min}$
  • 每个对象在MinWidth基础上额外获得按分配量对应比例计算得来的值,即获得的值为 $$ L_{r}\cdot{}W_{i}/\sum{}W_{i} $$
  • 例如上面第3个对象额外分配的宽度值即$(400-250)\cdot{}100/(150+50+100)=50$。加上MinWidth的50即100。

FlexibleWidth计算

再来看FlexibleWidth计算效果。如果进入该步骤,则说明一:布局器任然有空余控件,二:有设置FlexibleWidth的值。FlexibleWidth的计算是简单明了的。

即对于所有计算之后的剩余空间$L_{r}$,按照每个空间的FlexibleWidth值比例来进行分配。设每个子对象FlexibleWidth值为$F_{i}$即每个空间额外获得值为

$$ L_{r}\cdot{}F_{i}/\sum{}F_{i} $$

UseChildScale

是否使用子对象的放缩值,即Scale对象。UGUI在计算布局的时候,不会计算Scale对象的大小。即可认为Scale是修改的渲染尺寸,而不是UI的布局尺寸。所以会出现如下这样的布局情况:

中间元素LayoutElement为100但是有一个0.8的缩放。但是任然按照100来计算的布局参数。

勾选改参数后会使得计算布局的时候考虑Scale因子的缩放来进行计算。

ChildForceExpand

强制将子对象扩展,以使其填充整个布局区域。直觉上这个效果跟Flexible参数控制很类似。其实际效果相当于给每个组件的Flexible参数默认给了一个1。具体来说

  • 该子对象没设置Flexible值,则设置为1。
  • 如果子对象设置了Flexible值,则保持设置,

所以其具体计算效果,实际上,就是FlexibleWidth的效果来计算。

GridLayoutGroup

即网格布局器,其会将子对象像网格一样(即Grid样式)布局起来。其组件参数如下:

可以看到对比HorizontalLayoutGroup与VerticalLayoutGroup,其并没有根据子对象的 布局元素 来进行各种适配计算的结构。而是会使用GridLayoutGroup设定的参数值,无视子对象的LayoutElement,直接设置子对象的RectTransform的值。

  • Padding:相当于定义子对象的布局范围相对于当前区域Rect的上下左右距离。
  • CellSize:子对象Rect的宽高值。
  • Spacing:相邻子对象在横向,纵向之间的间距距离。

剩下四个参数,主要都是控制子对象的排序关系的。分别来说明:

  • StartCorner:

起始角落,一共有4个值。该值实际上指明了子对象在自己所张成空间内的布局顺序的起始角落。设置为LowerRight布局效果如下图:

可以看到其是从中间右下角开始,按向左顺序布局的。并不是从整个父节点的右下角开始布局。可以这么认为,先计算出所有子对象所占据的矩形空间,并从这个空间的四个角落为起点,开始按顺序布局子对象。

  • StartAxis:

开始轴向,有2个值Horizontal与Vertical。说明子对象是水平方向布局还是垂直方向布局。

  • ChildAlignment:

子对象的对齐位置,一共有9个值。这个值实际上指明了所有子对象所占据的矩形空间,相对于父空间的计算位置。例如MiddleCenter的情况,布局如下图:

从中间开始计算整个子对象矩形,同时将子对象矩形的中心点对齐在中心位置。这里可以认为对应9个值,相当于同时选定了子对象所占据的矩形空间的Pivot与Anchor为对应值。会尽量将所有子对象都布局在父对象的空间范围的内部。

  • Constraint:

约束条件,主要有3个值:

  • Flexible:不做约束,根据布局父子控件计算,如果对应StartAxis的方向超过了父对象的范围,则会进行换行,在另一个方向上进行延展。
  • FixedColumnCount/FixedRowCount:固定行或列的数量,只有超过该数量,则进行换行处理。即使该数值所形成的宽度大于父对象的范围,也会布局到该数量之后进行换行处理。

参考文档

Layout官方文档

LayoutElement: https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/script-LayoutElement.html

AutoLayout: https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/UIAutoLayout.html