属性: | 功能: |
---|---|
Aspect Mode | 如何调整矩形大小以强制执行宽高比。 |
None | 不要使rect适合宽高比。 |
Width Controls Height | 高度会根据宽度自动调整。 |
Height Controls Width | 宽度会根据高度自动调整。 |
Fit In Parent | 宽度,高度,位置和锚点会自动调整,以使rect适合在父对象的rect内部,同时保持宽高比。可能是父矩形内部的一些空间,该空间未被此矩形覆盖。 |
Envelope Parent | 宽度,高度,位置和锚点会自动调整,以使rect在保持宽高比的同时覆盖父对象的整个区域。该矩形的延伸范围可能比父矩形的延伸范围大。 |
Aspect Ratio | 要执行的宽高比。这是宽度除以高度。 |
上面是关于 Aspect Ratio Fitter 组件的一些官方解释信息,请先了解一下
个人见解:
-----底层代码------
///
/// The mode to use to enforce the aspect ratio.
///
public AspectMode aspectMode { get { return m_AspectMode; } set { if (SetPropertyUtility.SetStruct(ref m_AspectMode, value)) SetDirty(); } }
[SerializeField] private float m_AspectRatio = 1;
///
/// Mark the AspectRatioFitter as dirty.
///
protected void SetDirty()
{
UpdateRect();
}
在通过用户设置该属性时,就会把该物体的RectTransform 加入 DrivenRectTransformTracker 这样用户就无法修改了,然后去调用 SetDirty() 方法:
DrivenRectTransformTracker :驱动RectTransform意味着驱动的RectTransform的值由该组件控制。这些驱动值无法在检查器中编辑(显示为禁用)。保存场景时也不会保存它们,这可以防止意外的场景文件更改。
public struct DrivenRectTransformTracker
{
// 继续记录被驱动 RectTransforms 的撤消操作。
public static void StartRecordingUndo();
//
// 摘要:
// 停止记录被驱动 RectTransforms 中的撤消操作。
public static void StopRecordingUndo();
//
// 摘要:
// Add a RectTransform to be driven.
//
// 参数:
// driver:
// 驱动属性的对象。
//
// rectTransform:
// 要驱动的RectTransform。
//
// drivenProperties:
// 要驱动的属性。
public void Add(Object driver, RectTransform rectTransform, DrivenTransformProperties drivenProperties);
[Obsolete("revertValues parameter is ignored. Please use Clear() instead.")]
public void Clear(bool revertValues);
// 清除正在驱动的recttransform列表。
public void Clear();
}
private void UpdateRect()
{
if (!IsActive())
return;
m_Tracker.Clear();
switch (m_AspectMode)
{
#if UNITY_EDITOR
case AspectMode.None:
{
if (!Application.isPlaying)
// 默认为None时 限制其纵横比的范围
m_AspectRatio = Mathf.Clamp(rectTransform.rect.width / rectTransform.rect.height, 0.001f, 1000f);
break;
}
#endif
case AspectMode.HeightControlsWidth:
{
//添加到rectTransform设备追踪器,和要改变的属性
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
//根据改变的轴,更改物体的宽度
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rectTransform.rect.height * m_AspectRatio);
break;
}
case AspectMode.WidthControlsHeight:
{
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
//根据改变的轴,更改物体的高度
rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rectTransform.rect.width / m_AspectRatio);
break;
}
case AspectMode.FitInParent:
case AspectMode.EnvelopeParent:
{
m_Tracker.Add(this, rectTransform,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.AnchoredPosition |
DrivenTransformProperties.SizeDeltaX |
DrivenTransformProperties.SizeDeltaY);
//重置rectTransform的 默认值
rectTransform.anchorMin = Vector2.zero;
rectTransform.anchorMax = Vector2.one;
rectTransform.anchoredPosition = Vector2.zero;
Vector2 sizeDelta = Vector2.zero;
Vector2 parentSize = GetParentSize();
if ((parentSize.y * aspectRatio < parentSize.x) ^ (m_AspectMode == AspectMode.FitInParent))
{
//根据父物体的 高度和水平轴 获取自身的宽度值
sizeDelta.y = GetSizeDeltaToProduceSize(parentSize.x / aspectRatio, 1);
}
else
{
//根据父物体的 高度和垂直轴 获取自身的宽度值
sizeDelta.x = GetSizeDeltaToProduceSize(parentSize.y * aspectRatio, 0);
}
rectTransform.sizeDelta = sizeDelta;
break;
}
}
}
Content Size Fitter
组件主要是用来设置UI的长宽,Horizontal Fit
和 Vertical Fit
分别是控制UI的宽和高:
Unconstrained
:组件不根据布局元素调整 ,可手动修改长宽的值。MinSize
:根据布局元素的最小值来调整,不能手动修改长宽的值。PreferredSize
:根据布局元素的内容来调整,不能手动修改长宽的值。常用的是 PreferredSize 该属性随着物体的内容,自动调整物体的宽高,通常伴随着 其他Layout Group 组件一起使用
Content Size Fitter 用到到地方非常多,主要设置 图片和文字 跟随内容的变换改变自身的宽高。
比如:在答案的最右侧添加一个按钮,用来点击显示答案,我们可以使用 Horizontal Fit 属性的 PreferredSize ,然后再去设置子物体的锚点或中心点
子物体设置器锚点为右对齐
具体效果如下:
2.还可以设置为文字背景随着文字变化而变化,这样需要子物体添加 Content Size Fitter 组件 并把水平和垂直 都设置为 PreferredSize
父物体也需要添加 Content Size Fitter组件 并且 需要添加 Vertical Layout Group 组件来控制子物体的变换
具体效果:(在编辑器下进行会有些延迟)
其底层原来和 Aspect Ratio Fitter 基本相似
属性: | 功能: |
---|---|
Ignore Layout | 启用后,布局系统将忽略此布局元素。 |
Min Width | 此布局元素应具有的最小宽度。 |
Min Height | 此布局元素应具有的最小高度。 |
Preferred Width | 在分配其他可用宽度之前,此布局元素应具有的首选宽度。 |
Preferred Height | 在分配其他可用高度之前,此布局元素应具有的首选高度。 |
Flexible Width | 此布局元素应相对于其同级元素填充的额外可用宽度的相对数量。 |
Flexible Height | 此布局元素应相对于其同级元素填充的额外可用高度的相对数量。 |
Layout Priority | 此组件的布局优先级。 如果GameObject具有一个以上具有布局属性的组件(例如Image组件和LayoutElement组件),则布局系统将使用Layout Priority最高的组件的属性值。 如果组件具有相同的Layout Priority,则布局系统将为每个属性使用最大值,而不管其来自哪个组件。 |
布局元素包括7个属性,其中前6个属性是每个布局元素自身大小信息的设定,一般用于布局控制器对其进行大小和位置的设定。
1.布局涉及两个核心要件,包括布局控制器(Layout Controller)和布局元素,其中布局控制器包括水平布局组、垂直布局组和网格布局组;布局元素(Layout Element)是那些含Rect Transform组件的对象,而不仅仅是那些含Layout Element组件的对象。
2.布局元素不会变动自己的大小,而是由布局组(布局控制器)来根据布局元素的尺寸属性(最小的宽高(根据像素大小设定)、最适宜的宽高(根据像素大小设定)、可伸缩的宽高(根据权重来设定))来分配对应的尺寸及所在位置。布局组(布局控制器)会满足所有布局元素的最小尺寸。
3.Flexible Width和Flexible Height是通过权重来设定伸缩尺寸,即如果布局组已经将所有布局元素的最适宜宽高配置完后还有剩余空间,则会将剩余控件根据每个布局元素的可伸缩控件权重来分配剩余的空间。
4.Ignore Layout,即本布局元素不参与布局组的布局。
Layout Element
包含的信息不会直接改变物体的大小。而是,通过 Layout Controller 组件去读取物体的Layout Element 值,
然后控制一个UI元素实际的大小的。
例如:
我们把元素布局的 Preferred Width 和 Preferred Height 设置为150 默认是120
unity的LayoutGroup分为三种, Horizontal Layout Group(水平布局)Vertical Layout Group(垂直布局)Grid Layout Group (网格布局)
前两种布局是控制子物体的垂直和水平的布局组件,相对于Grid Layout Group 它只能垂直或者水平去区布局,不能分多行或多列显示
只能应用相对单一的布局
VerticalLayoutGroup 、 Horizontal Layout Group 都是继承了 HorizontalOrVerticalLayoutGroup 类 通过传入的 轴值和 isVertical 布尔值 进行区分,HorizontalOrVerticalLayoutGroup 有继承LayoutGroup 类 ,LayoutGroup 类主要控制 组件的参数属性和物体的RectTransform属性的获取
HorizontalOrVerticalLayoutGroup 类 是布局控制的中心根据不同的参数值改变相应的布局方式
protected void SetChildrenAlongAxis(int axis, bool isVertical)
{
//获取组件信息
float size = rectTransform.rect.size[axis];
bool controlSize = (axis == 0 ? m_ChildControlWidth : m_ChildControlHeight);
bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
bool childForceExpandSize = (axis == 0 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);
//确定对齐方式 以分数形式返回指定轴上的对齐方式,其中0为左/上、0.5为中、1为右/下。
float alignmentOnAxis = GetAlignmentOnAxis(axis);
bool alongOtherAxis = (isVertical ^ (axis == 1)); //异或运算 相同返回 0 ,反之为 1
if (alongOtherAxis)
{
//
float innerSize = size - (axis == 0 ? padding.horizontal : padding.vertical);
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
float min, preferred, flexible;
//获取子物体的属性信息
GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);
//是否使用子物体的缩放值
float scaleFactor = useScale ? child.localScale[axis] : 1f;
//判断元素宽高是否大于元素的最小值且小于灵活值或优先值,得到元素的必需尺寸
float requiredSpace = Mathf.Clamp(innerSize, min, flexible > 0 ? size : preferred);
//获取偏移量
float startOffset = GetStartOffset(axis, requiredSpace * scaleFactor);
//得到以上参数值后,最后一步进行计算子物体的大小及位置
if (controlSize)
{
SetChildAlongAxisWithScale(child, axis, startOffset, requiredSpace, scaleFactor);
}
else
{
float offsetInCell = (requiredSpace - child.sizeDelta[axis]) * alignmentOnAxis;
SetChildAlongAxisWithScale(child, axis, startOffset + offsetInCell, scaleFactor);
}
}
}
else
{
float pos = (axis == 0 ? padding.left : padding.top);
float itemFlexibleMultiplier = 0;
//获取剩余空间 父物体尺寸减去总的首选尺寸
float surplusSpace = size - GetTotalPreferredSize(axis);
//根据剩余空间的值,来确定剩余的空间平均分配到每个元素上的值
if (surplusSpace > 0)
{
if (GetTotalFlexibleSize(axis) == 0)
pos = GetStartOffset(axis, GetTotalPreferredSize(axis) - (axis == 0 ? padding.horizontal : padding.vertical));
else if (GetTotalFlexibleSize(axis) > 0)
itemFlexibleMultiplier = surplusSpace / GetTotalFlexibleSize(axis);
}
float minMaxLerp = 0;
if (GetTotalMinSize(axis) != GetTotalPreferredSize(axis))
minMaxLerp = Mathf.Clamp01((size - GetTotalMinSize(axis)) / (GetTotalPreferredSize(axis) - GetTotalMinSize(axis)));
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
float min, preferred, flexible;
GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);
float scaleFactor = useScale ? child.localScale[axis] : 1f;
float childSize = Mathf.Lerp(min, preferred, minMaxLerp);
childSize += flexible * itemFlexibleMultiplier;
if (controlSize)
{
SetChildAlongAxisWithScale(child, axis, pos, childSize, scaleFactor);
}
else
{
float offsetInCell = (childSize - child.sizeDelta[axis]) * alignmentOnAxis;
SetChildAlongAxisWithScale(child, axis, pos + offsetInCell, scaleFactor);
}
pos += childSize * scaleFactor + spacing;
}
}
}
属性: | 功能: |
---|---|
Padding | 布局组边缘内的填充。 |
Cell Size | 组中每个布局元素要使用的大小。 |
Spacing | 布局元素之间的间距。 |
Start Corner | 第一个元素所在的角。 |
Start Axis | 沿着哪个主轴放置元素。在开始新行之前,水平将填满整个行。在开始新列之前,Vertical将填充整个列。 |
Child Alignment | 如果布局元素未填满所有可用空间,则用于这些元素的对齐方式。 |
Constraint | 将网格限制为固定数量的行或列,以辅助自动布局系统。
|
Grid Layout Group 网格布局组 将忽略其所包含布局元素的最小,首选和灵活大小属性,而是为所有这些元素分配固定大小,就是 子物体大小只能通过Cell Size 进行修改。
这里主要说一下,Start Corner 和 Start Axis
Start Corner:第一个元素所在的位置,就是元素开始排放的位置。一共是四个角,左上,右上,左下,右下
Start Axis : 开始填充元素的优先填充选定的轴。
Start Axis :Horizontal
Start Axis :vertical
private void SetCellsAlongAxis(int axis)
{
if (axis == 0)
{
// Only set the sizes when invoked for horizontal axis, not the positions.
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform rect = rectChildren[i];
//将子物体的RectTransform 添加到设备追踪器并添加要修改的属性
m_Tracker.Add(this, rect,
DrivenTransformProperties.Anchors |
DrivenTransformProperties.AnchoredPosition |
DrivenTransformProperties.SizeDelta);
rect.anchorMin = Vector2.up;
rect.anchorMax = Vector2.up;
rect.sizeDelta = cellSize;
}
return;
}
float width = rectTransform.rect.size.x;
float height = rectTransform.rect.size.y;
int cellCountX = 1;
int cellCountY = 1;
if (m_Constraint == Constraint.FixedColumnCount)
{
cellCountX = m_ConstraintCount;
if (rectChildren.Count > cellCountX)
//再确定列数后,如果子物体的数量大于指定的列,根据总数量除以列数加上总数量取余列数是否大于0 是就返回1 否返回 0。来确定元素的行数
cellCountY = rectChildren.Count / cellCountX + (rectChildren.Count % cellCountX > 0 ? 1 : 0);
}
else if (m_Constraint == Constraint.FixedRowCount)
{
cellCountY = m_ConstraintCount;
//再确定行数后,如果子物体的数量大于指定的行,根据总数量除以行数加上总数量取余行数是否大于0 是就返回1 否返回 0。来确定元素的列数
if (rectChildren.Count > cellCountY)
cellCountX = rectChildren.Count / cellCountY + (rectChildren.Count % cellCountY > 0 ? 1 : 0);
}
else
{
if (cellSize.x + spacing.x <= 0)
cellCountX = int.MaxValue;
else
//选择灵活布局时,需要判断父物体的宽度减去水平轴的偏移量加上元素间隔 ,然后除以 元素的宽度和间隔值 的最大 int 值。得到x 轴需要放置几个元素
cellCountX = Mathf.Max(1, Mathf.FloorToInt((width - padding.horizontal + spacing.x + 0.001f) / (cellSize.x + spacing.x)));
if (cellSize.y + spacing.y <= 0)
cellCountY = int.MaxValue;
else
//选择灵活布局时,需要判断父物体的高度减去垂直轴的偏移量加上元素间隔 ,然后除以 元素的宽度和间隔值 。得到y 轴需要放置几个元素
cellCountY = Mathf.Max(1, Mathf.FloorToInt((height - padding.vertical + spacing.y + 0.001f) / (cellSize.y + spacing.y)));
}
int cornerX = (int)startCorner % 2;
int cornerY = (int)startCorner / 2;
int cellsPerMainAxis, actualCellCountX, actualCellCountY;
//在根据不同的参数得到x,y 轴需要的元素数量后,再根据startAxis 轴 ,并进行计算在是否限定1和元素总数之间,然后返回x,y的值
if (startAxis == Axis.Horizontal)
{
cellsPerMainAxis = cellCountX;
actualCellCountX = Mathf.Clamp(cellCountX, 1, rectChildren.Count);
actualCellCountY = Mathf.Clamp(cellCountY, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis));
}
else
{
cellsPerMainAxis = cellCountY;
actualCellCountY = Mathf.Clamp(cellCountY, 1, rectChildren.Count);
actualCellCountX = Mathf.Clamp(cellCountX, 1, Mathf.CeilToInt(rectChildren.Count / (float)cellsPerMainAxis));
}
//知道x,y 的元素数量后,进行计算 总共所需的空间大小 和 偏移量
Vector2 requiredSpace = new Vector2(
actualCellCountX * cellSize.x + (actualCellCountX - 1) * spacing.x,
actualCellCountY * cellSize.y + (actualCellCountY - 1) * spacing.y
);
Vector2 startOffset = new Vector2(
GetStartOffset(0, requiredSpace.x),
GetStartOffset(1, requiredSpace.y)
);
//最后在通过 以上的值进行计算每个元素的位置
for (int i = 0; i < rectChildren.Count; i++)
{
int positionX;
int positionY;
if (startAxis == Axis.Horizontal)
{
positionX = i % cellsPerMainAxis;
positionY = i / cellsPerMainAxis;
}
else
{
positionX = i / cellsPerMainAxis;
positionY = i % cellsPerMainAxis;
}
if (cornerX == 1)
positionX = actualCellCountX - 1 - positionX;
if (cornerY == 1)
positionY = actualCellCountY - 1 - positionY;
SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (cellSize[0] + spacing[0]) * positionX, cellSize[0]);
SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]);
}
}