Unity的UGUI自动布局小功能组件制作及学习历程(2)

1.前言

通过上次的学习,我们已经了解了各个自动布局组件的功能和用法,

以及为什么不能用已有的自动布局组件达成我们的需求。这次我们就要为了达成需求而查看这些自动布局组件的源代码。

源代码的目录在 游戏安装目录\Editor\Data\Resources\PackageManager\BuiltInPackages\com.unity.ugui

Editor代码在Editor目录下,例如Editor\UI\GridLayoutGroupEditor.cs

正常脚本代码在Runtime\UI\Core\Layout\GridLayoutGroup。

2.代码结构

从GridLayoutGruop和HorizontalLayoutGroup的父类可以看出都是继承了LayoutGroup抽象类,需要实现四个方法。

//public class HorizontalLayoutGroup : HorizontalOrVerticalLayoutGroup
//public abstract class HorizontalOrVerticalLayoutGroup : LayoutGroup
// public class GridLayoutGroup : LayoutGroup

//以上是源码继承的结构
//以下是实现的四个方法名
//这个看起来不是强制实现,实际上是因为需要在这个方法里去计算子类的数量等操作,类似与初始化方法,
//所以还是要重写这个方法,并且加上一句代码
//方法1
	public override void CalculateLayoutInputHorizontal()
    {
        base.CalculateLayoutInputHorizontal();
    }
//这三个是强制实现
//方法2
	public override void CalculateLayoutInputVertical()
    {
        throw new System.NotImplementedException();
    }
//方法3
    public override void SetLayoutHorizontal()
    {
        throw new System.NotImplementedException();
    }
//方法4
    public override void SetLayoutVertical()
    {
        throw new System.NotImplementedException();
    }
//通过断点调试我们能看到在LayoutRebuilder脚本中的以下方法
//从这个方法我们可以推断出,这个脚本会自动顺序执行所有继承LayoutGruop类的4个方法。
//先是执行场景中所有自动布局组件的方法1。再执行方法3,再执行方法2,最后执行方法4。
//
		public void Rebuild(CanvasUpdate executing)
        {
            switch (executing)
            {
                case CanvasUpdate.Layout:
                    // It's unfortunate that we'll perform the same GetComponents querys for the tree 2 times,
                    // but each tree have to be fully iterated before going to the next action,
                    // so reusing the results would entail storing results in a Dictionary or similar,
                    // which is probably a bigger overhead than performing GetComponents multiple times.
                    PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());
                    PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());
                    PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());
                    PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());
                    break;
            }
        }

单纯从代码上看,很难看出各个方法的具体作用,耗费时间也是非常长的,当时也是花了好几个小时不断的调试和阅读代码,才理解了各个方法的作用。在这里我直接进行解释。

  1. 方法1和2有设定边界的作用,即在挂载Content Size Fitter后,可以自动的计算大小。计算的大小就是通过方法1,2返回的,通过调用LayoutGroup父类中的SetLayoutInputForAxis进行设置。参数为float totalMin, float totalPreferred, float totalFlexible, int axis。4个值,结合上篇文章的3个属性就可以明白具体的用法了。axis是设置方向,0和横,1为纵。

    如果没有挂载Content Size Fitter脚本,那么设置的值并不会起到任何作用,但是该方法依旧是会被执行的。

  2. 方法3和方法4有设置每一个子物体位置的作用。通常遍历所有的子物体,对每个子物体有个算法去计算他的位置和大小(大小需要额外展开描述)。计算完后可以通过4个已经给的方法进行设置。

    protected void SetChildAlongAxis(RectTransform rect, int axis, float pos)
    protected void SetChildAlongAxisWithScale(RectTransform rect, int axis, float pos, float scaleFactor)
    //以上两个方法是不对物体大小进行设置的,通常是不固定子物体的大小,
    //第一个方法是对第二个方法的封装,最后一个参数为1.0f。
    //以下两个方法是会对物体大小进行设置的,固定子物体的大小,会锁定子物体大小的修改,
    //第一个方法是对第二个方法的封装,最后一个参数是1.0f。    
    protected void SetChildAlongAxis(RectTransform rect, int axis, float pos, float size)
    protected void SetChildAlongAxisWithScale(RectTransform rect, int axis, float pos, float size, float scaleFactor)
    //锁定某一个UGUI的rect参数采用以下方式,很好理解。
    m_Tracker.Add(this, rect,
                    DrivenTransformProperties.Anchors |
                    (axis == 0 ?
                        (DrivenTransformProperties.AnchoredPositionX | DrivenTransformProperties.SizeDeltaX) :
                        (DrivenTransformProperties.AnchoredPositionY | DrivenTransformProperties.SizeDeltaY)
                    )
                );
    //感兴趣实现方式可以自行阅读源码,带着我给的大致理解思路可以更为清晰的阅读代码。
    //如果这些方法并不能达成自己的目标,也可以照着样子自己去写一个设置位置的方法。
    
  3. 可能会不太容易理解这个axis,其实这个axis就是代表这横纵关系。0就是width,1就是height。从Horizontal走一般axis就是为0,代表设置width,另一个就是1,代表设置height。每次都单纯的设置一个width或者height,不会两个同时设置。

知道以上几点就能理解该组件带来的主要内容了,但是每个组件都有其特殊控制的部分。首先是LayoutGroup所附带的,每个组件都具有的两个东西。

[SerializeField] protected RectOffset m_Padding = new RectOffset();

        /// <summary>
        /// The padding to add around the child layout elements.
        /// </summary>
        public RectOffset padding { get { return m_Padding; } set { SetProperty(ref m_Padding, value); } }
//其中可以获取上下左右的各项值和横竖总值,这个是需要参与进各项的计算的。


[SerializeField] protected TextAnchor m_ChildAlignment = TextAnchor.UpperLeft;

        /// <summary>
        /// The alignment to use for the child layout elements in the layout group.
        /// </summary>
        /// <remarks>
        /// If a layout element does not specify a flexible width or height, its child elements many not use the available space within the layout group. In this case, use the alignment settings to specify how to align child elements within their layout group.
        /// </remarks>
        public TextAnchor childAlignment { get { return m_ChildAlignment; } set { SetProperty(ref m_ChildAlignment, value); } }
//代表对齐的各个方向。
//有个这个对齐的方向,就还需要了解一个已经给的api :
//GetStartOffset(int axis, float requiredSpaceWithoutPadding)
//给出方向和这个方向里子物体所占据的位置,就能得到偏移值,通过这个偏移值来计算,
//就能容易的获取到起始位置,也就能达到对齐的效果。如果想看对齐的算法可以自行查看。

所设计提供给外部的每个内容都要有其合理性,需要参与进计算的。继续来看HorizontalLayoutGroup,上面有三个比较核心的点。首先得知道这3个核心点都是做什么的,这个在上一篇文章已经讲到了,知道了功能,那么设计就很方便了,因为在挂载了自动布局组件的物体的子物体是不能挂载Content Size Fitter脚本的,所以需要有一个参数去自动判断设置子物体的大小。写一个获取子物体min和preferred的方法:

GetChildSizes(RectTransform child, int axis, bool controlSize, bool childForceExpand,
            out float min, out float preferred, out float flexible)
 //如果controlSize == false,那么直接返回子物体大小即可,如果不是,需要通过LayoutUtility.GetMinSize(child,axis)去得到min等值,然后再通过SetChildAlongAxisWithScale(RectTransform rect, int axis, float pos, float size, float scaleFactor)进行设置。ChildForceExpand这个参数是协助计算的,还是需要知道功能后进行特殊判断等操作。
//每个写出来的方法顺序什么的需要把握好。知道用法再去看源代码,这样理解吸收的就会非常快了,然后反过来再设计自己的东西,对有疑惑的地方再去看一眼源码,其实也没有其他什么特别难的东西。
    

而GridLayoutGroup中几个新的参数,也是知道其功能后去设计起来就非常容易了,功能在上一篇文章中也都有讲到,比较推荐去看一下算法的源代码,加深一下思路。

具体的算法源码并不需要去仔细的说明,因为思路都已经讲清了,自己通过思路去查看源代码会比讲解更加的让人印象深刻。下一篇文章将会讲解面向项目的一些设计思路,在阅读之前,也可以自己去尝试一下写一写,主要任务还是实现4个方法,实现并跑通即可。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值