Unity 常用布局组件的使用 ~ 浅谈理解

 

1.Aspect Ratio Fitter   纵横比拟合器

    

属性:

属性:功能:
Aspect Mode如何调整矩形大小以强制执行宽高比。
 None不要使rect适合宽高比。
Width Controls Height高度会根据宽度自动调整。
 Height Controls Width宽度会根据高度自动调整。
 Fit In Parent宽度,高度,位置和锚点会自动调整,以使rect适合在父对象的rect内部,同时保持宽高比。可能是父矩形内部的一些空间,该空间未被此矩形覆盖。
Envelope Parent宽度,高度,位置和锚点会自动调整,以使rect在保持宽高比的同时覆盖父对象的整个区域。该矩形的延伸范围可能比父矩形的延伸范围大。
Aspect Ratio要执行的宽高比。这是宽度除以高度。

 

 

 

 

 

 

 

 

上面是关于 Aspect Ratio Fitter 组件的一些官方解释信息,请先了解一下

个人见解:

  1. None   不使用纵横比  
  2. Width Controls Height 和 Height Controls Width   :  是根据当前的宽度或高度,再根据比率去计算相对于的高度或宽度,使其保持指定的比率
    1. 比如说:当UI给我们的图片大小不一致,但显示的背景图是确定的,为了保证图片显示效果正常(指定高度或宽度一致的情况下),我们可以使用 Width Controls Height 或 Height Controls Width ,保证图片不被拉伸我们可以数值纵横比为 1   ,这样就可以保障图片指定高度或宽度显示,还保证图片显示效果正常。
  3. Fit In Parent   :  使用这个属性时,会保证图片不会超过父物体的宽高情况下,子物体保持拟合(跟父物体的宽或高保持一致)同时保证纵横比,此时子物体大小,宽高,位置会改变
    1. 比如我们拿到一个1080*1920 分辨率的视频,为了让它保证保证显示正常且在指定的播放背景下进行播放,此时我们可以使用该属性,定义它的比率(width/hight )为 1920/1080 约等于 1.777778 时,这样不管父物体怎么变换,该视频播放就会保证在指定的分辨率下进行播放。
    2. 同理,在我们开发中,如果一些指定分辨率或宽高比的需求,都可以使用此属性
    3.  

  4. Envelope Parent  :  这个属性与上面的正好相反,是保证覆盖父物体的情况下保持纵横比,此时子物体大小,宽高,位置也会改变。
    1. 这个属性用到地方很少(至少目前我还没用过),基本使用原理跟上面类似,如果你遇到既保持从横比 又要覆盖父物体时,可以使用此属性。

-----底层代码------


        /// <summary>
        /// The mode to use to enforce the aspect ratio.
        /// </summary>
        public AspectMode aspectMode { get { return m_AspectMode; } set { if (SetPropertyUtility.SetStruct(ref m_AspectMode, value)) SetDirty(); } }

        [SerializeField] private float m_AspectRatio = 1;

        /// <summary>
        /// Mark the AspectRatioFitter as dirty.
        /// </summary>
        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;
                    }
            }
        }

2.  Content Size Fitter

Content Size Fitter 组件主要是用来设置UI的长宽,Horizontal Fit 和 Vertical Fit 分别是控制UI的宽和高:

  1. Unconstrained:组件不根据布局元素调整 ,可手动修改长宽的值。
  2. MinSize:根据布局元素的最小值来调整,不能手动修改长宽的值。
  3. PreferredSize:根据布局元素的内容来调整,不能手动修改长宽的值。

常用的是 PreferredSize 该属性随着物体的内容,自动调整物体的宽高,通常伴随着 其他Layout Group 组件一起使用

  1. 具体使用中 Content Size Fitter 用到到地方非常多,主要设置 图片和文字 跟随内容的变换改变自身的宽高。

比如:在答案的最右侧添加一个按钮,用来点击显示答案,我们可以使用 Horizontal Fit 属性的 PreferredSize ,然后再去设置子物体的锚点或中心点

子物体设置器锚点为右对齐

具体效果如下:

 2.还可以设置为文字背景随着文字变化而变化,这样需要子物体添加 Content Size Fitter 组件 并把水平和垂直 都设置为 PreferredSize

 

父物体也需要添加 Content Size Fitter组件 并且 需要添加 Vertical Layout Group 组件来控制子物体的变换

具体效果:(在编辑器下进行会有些延迟)

其底层原来和  Aspect Ratio Fitter 基本相似

3.Layout Element 布局 元素

 

属性:功能:
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 

从上图可以看到,如果 Min Width和 Min Height 优先级最高,Preferred Width 、Preferred Height 是父物体保证空间位置允许的情况下 采用的首选宽高。

而 Flexible Height 、Flexible Width 则是以 填充的方式去根据该值分割父物体的空间

 

4. VerticalLayoutGroup 、  Horizontal Layout Group 垂直布局

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;
                }
            }
        }

5. Grid Layout Group   网格布局

 

属性:功能:
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]);
            }
        }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值