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

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


前言

通过前两篇文章的讲解,我们已经了解了制作需求的全部前置知识,有了完全这个需求的能力了。在制作之前,需要进行程序视角的需求分析和设计。

需求分析和设计

需求介绍

假设项目中有一个UI公告板的需求,这个公告板的长度或者宽度是固定的。里面有很多带文字的新闻,但是这些个新闻的字数和大小是不固定的,要求模仿自动布局组件,写一个组件满足UI的需求。

需求分析

首先是长度或者宽度是固定,这就意味这不固定的另一边可以动态延申,这就要求我们必须实现Calculate。。。的那几个方法。

第二个是内部的大小不固定,那么我们要实现自动控制孩子大小的功能,来动态的控制孩子的大小,所以参数上control child需要有。

第三个是继承LayoutGroup之后,那两个参数也是需要实现的,但是由于功能的特殊性,导致的是需要特殊的处理方法。

第三个是要控制孩子的换行,而且换行之后还得找到之前一行的最长的另一边,这样才能让不和之前一行的交叉。

因为要又知道行,又知道宽,这和之前给定的分割宽高设计完全不同了,庆幸的是,我们的长度或者宽度是完全固定的。

设计

  1. 肯定是要优先实现两个Cal方法,这个方法的主要目的是设置总长和总宽,因为需求中长度或者宽度是固定,也就意味着只有两种情况,在长度是固定时,我们直接设置长度为原来的长度即可,宽度通过计算得出;在宽度时固定时,我们直接设置宽度为原来的宽度即可,长度通过计算得出。

    //本质是代表拓宽,但是startAxis代表格子方向,所以刚好和系统设计的相反,这个需要理解。 
    public override void CalculateLayoutInputHorizontal()
        {
            base.CalculateLayoutInputHorizontal();
            if (startAxis == Axis.Vertical)
                SetRange(1);
            else
            {
                //直接设置长或宽
                float length = rectTransform.rect.size[0];
                needOtherLength = length;
                SetLayoutInputForAxis(length, length, -1, 0);
            }
        }
    public override void CalculateLayoutInputVertical()
        {
            if (startAxis == Axis.Horizontal)
                SetRange(0);
            else
            {
                float length = rectTransform.rect.size[1];
                needOtherLength = length;
                SetLayoutInputForAxis(length, length, -1, 1);
            }
        }
    
  2. 在设置每个孩子大小的时候,也是在一次同时计算长和宽,在另一次就不会计算。由于两个方法都要执行,我们就采用startAxis判断就行了。

public override void SetLayoutHorizontal()
    {
        if (startAxis == Axis.Horizontal)
            SetChildrenAlongAxis(0);
    }

public override void SetLayoutVertical()
    {
        if (startAxis == Axis.Vertical)
            SetChildrenAlongAxis(1);
    }
  1. 所以我们只要去实现SetRange和SetChildrenAlongAxis方法即可。

    1. 需要去考虑几个参数点,一个是是否控制大小,一个是padding,一个是spacing,一个是ChildAliment(设置大小的时候不用考虑),又考虑到,我们在计算范围大小的时候,是不是要计算并得到每个格子的大小,那么我们就可以顺便计算出格子大小,在计算格子大小的时候直接赋值。

    2. 我们要得到min值和preferred的计算结果对比大小

    3. 以行拓展为例,列长度是固定的,所以我们先计算每一列能容下几个,然后得到这几个中最大的宽度,换行,遍历所有计算,就可以得到所需的结果。

      protected void SetRange(int axis)
          {
              float combinedPadding = (axis == 1 ? padding.horizontal : padding.vertical);
      
              float innerSize = axis == 0 ? rectTransform.rect.width - padding.horizontal : rectTransform.rect.height - padding.vertical;
              
              bool controlSize = (axis == 0 ? m_ChildControlWidth : m_ChildControlHeight);
              bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
              bool childForceExpandSize = (axis == 0 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);
      
              bool otherAxisControlSize = (axis == 1 ? m_ChildControlWidth : m_ChildControlHeight);
              bool otherAxisUseScale = (axis == 1 ? m_ChildScaleWidth : m_ChildScaleHeight);
              bool oterAxisChildForceExpandSize = (axis == 1 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);
      
      
              float spacingLength = spacing[axis];
              float spacingOtherLength = spacing[axis ^ 1];
      
              float maxCellMinLength = -999;
              float maxCellPreferredLength = -999;
              float totalOneMinLength = - spacingLength;
              float totalOnePreferredLength = - spacingLength;
              float needMinLength = combinedPadding;
              float needPreferredLength = combinedPadding;
              float totalFlexible = 0;
      
      
              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);
                  min = min == 0 ? child.sizeDelta[axis] : min;
                  preferred = preferred == 0 ? child.sizeDelta[axis] : preferred;
                  min = Mathf.Clamp(min, 0, innerSize);
                  preferred = Mathf.Clamp(preferred, 0, innerSize);
      
                  float otherAxisMin, otherAxisPreferred, otherAxisFlexible;
                  GetChildSizes(child, axis ^ 1, otherAxisControlSize, oterAxisChildForceExpandSize, out otherAxisMin, out otherAxisPreferred, out otherAxisFlexible);
                  otherAxisMin = otherAxisMin == 0 ? child.sizeDelta[axis ^ 1] : otherAxisMin;
                  otherAxisPreferred = otherAxisPreferred == 0 ? child.sizeDelta[axis ^ 1] : otherAxisPreferred;
      
                  if (useScale)
                  {
                      float scaleFactor = child.localScale[axis];
                      min *= scaleFactor;
                      preferred *= scaleFactor;
                      flexible *= scaleFactor;
                  }
      
                  if (otherAxisUseScale)
                  {
                      float scaleFactor = child.localScale[axis ^ 1];
                      otherAxisMin *= scaleFactor;
                      otherAxisPreferred *= scaleFactor;
                      otherAxisFlexible *= scaleFactor;
                  }
      
                  if (controlSize)
                  {
                      float size = min > preferred ? min : preferred;
                      if (size == 0)
                      {
                          size = GetChildSizesAfterCal(child, axis);
                      }
                      SetChildSize(child, axis, size);
                  }
      
                  if (otherAxisControlSize)
                  {
                      float size = otherAxisMin > otherAxisPreferred ? otherAxisMin : otherAxisPreferred;
                      if (size == 0)
                      {
                          size = GetChildSizesAfterCal(child, axis ^ 1);
                      }
                      SetChildSize(child, axis ^ 1, size);
                  }
                  //算min的一个情况,当一边加起来大于容量,就会自动跳转,选择上次最大的长或宽
                  if (totalOneMinLength + min + spacingLength > innerSize && totalOneMinLength != 0)
                  {
                      needMinLength += maxCellMinLength + spacingOtherLength;
                      totalFlexible += flexible;
                      maxCellMinLength = otherAxisMin;
                      totalOneMinLength = min + (axis == 0 ? padding.top : padding.left);
                  }
                  else
                  {
                      totalOneMinLength += min + spacingLength;
                      if (maxCellMinLength < otherAxisMin)
                      {
                          maxCellMinLength = otherAxisMin;
                      }
                  }
                  //算preferred的一个情况
                  if (totalOnePreferredLength + preferred + spacingLength > innerSize && totalOnePreferredLength != 0)
                  {
                      needPreferredLength += maxCellPreferredLength + spacingOtherLength;
                      maxCellPreferredLength = otherAxisPreferred;
                      totalOnePreferredLength = preferred + (axis == 0 ? padding.top : padding.left);
                  }
                  else
                  {
                      totalOnePreferredLength += preferred + spacingLength;
                      if (maxCellPreferredLength < otherAxisPreferred)
                      {
                          maxCellPreferredLength = otherAxisPreferred;
                      }
                  }
              }
              needMinLength += maxCellMinLength;
              needPreferredLength += maxCellPreferredLength;
              //选择最大的一条然后设置
              needLength = needMinLength > needPreferredLength ? needMinLength : needPreferredLength;
              SetLayoutInputForAxis(needMinLength, needPreferredLength, totalFlexible, axis ^ 1);
          }
      
  2. 计算完范围大小和取得对应长度以后,我们就可以设置每一个物体的相应位置了。如果要问为什么不一边计算大小一边设置位置,理论上来说是可以的,但是还是适应这个框架,让更多人理解和看懂,也不会让一个方法过于繁杂。

    public override void SetLayoutHorizontal()
        {
            if (startAxis == Axis.Horizontal)
                SetChildrenAlongAxis(0);
        }
    
        public override void SetLayoutVertical()
        {
            if (startAxis == Axis.Vertical)
                SetChildrenAlongAxis(1);
        }
    protected void SetChildrenHorizontalAndVerticalAlongAxis(int axis)
        {
    
            bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
     
            bool otherAxisUseScale = (axis == 1 ? m_ChildScaleWidth : m_ChildScaleHeight);
    
            Vector2 requireSpace = new Vector2(axis == 0? needOtherLength : needLength, axis == 0 ? needLength : needOtherLength);
            Vector2 startOffset = new Vector2(GetStartOffset(0, requireSpace.x), GetStartOffset(1, requireSpace.y));
            //初始化的一开始的地方,这个startoffset就是父类中的一个方法得到的,用于解决,ChildAliment问题。
        	//spacing和padding计算大小和位置都有用到,需要理解用法。
            float initLength = (axis == 0 ? padding.left : padding.top) + startOffset[axis];
            float initOtherLength = (axis == 0 ? padding.top : padding.left) + startOffset[axis ^ 1];
            //一行/列的暂时总长
            float tempLength = initLength;
            float tempOtherLength = initOtherLength;
            float tempMaxOtherLength = float.MinValue;
            float Fatherlength = axis == 0 ? rectTransform.rect.width : rectTransform.rect.height;
            float spacingLength = spacing[axis];
            float spacingOtherLength = spacing[axis ^ 1];
    
            for (int i = 0; i < rectChildren.Count; i++)
            {
                RectTransform child = rectChildren[i];
    
                float innerSize = rectTransform.rect.size[axis] - (axis == 0 ? padding.horizontal : padding.vertical);
    
                float childOldOtherLength = GetChildSizesAfterCal(child, axis ^ 1);
                float childLength = GetChildSizesAfterCal(child, axis);
                float childScale = useScale ? child.localScale[axis] : 1f;
                float childOtherAxisScale = otherAxisUseScale ? child.localScale[axis ^ 1] : 1f;
    
    
                childLength = childLength * childScale;
    
                float allLength = (((tempLength + childLength) > Fatherlength) && tempLength == initLength) ? Fatherlength : tempLength + childLength;
                //发现这个加进去计算后已经超出了最大范围,那么重新设置一些值并且回退重新计算这个值
                
                if (Mathf.Round(allLength * 10000) / 10000 > Mathf.Round(Fatherlength * 10000) / 10000)
                {
    
                    i--;
                    tempLength = initLength;
                    tempMaxOtherLength *= childOtherAxisScale;
                    tempOtherLength += tempMaxOtherLength + spacingOtherLength;
                    tempMaxOtherLength = float.MinValue;
                }
                else
                {
    
                    if (childOldOtherLength * childOtherAxisScale > tempMaxOtherLength)
                    {
                        tempMaxOtherLength = childOldOtherLength * childOtherAxisScale;
    
                    }
                    SetChildPos(rectChildren[i], axis, tempLength);
                    SetChildPos(rectChildren[i], axis ^ 1, tempOtherLength);
    
                    tempLength += childLength + spacingLength;
    
                }
            }
        }
    
  3. 演示图
    Unity的UGUI自动布局小功能组件制作及学习历程(3)_第1张图片

结语

通过这个需求,我们深刻理解了Unity提供的Layout Group类的用法,也能自己写相关的自动布局组件,还锻炼了自己计算范围和大小的思维能力,在将来编写类似的自动布局组件更加得心应手了,可以说是收获颇丰。源代码文件我会放在下载里,请有需要的自取支持一下。

你可能感兴趣的:(Unity的UGUI自动布局,unity,学习,ui)