【Unity】UI自定义自动布局
由于某些需求有一个使用GridLayoutGroup组件无法达到对应的布局效果,本来打算使用提前定义好结点后再在对应的结点上进行一个添加的方式,不过在和主程沟通后得知可以自定义一个LayoutGroup,于是就去看了相关资料。
这里先提一下相关资料:
UGUI源码分析:CanvasUpdateSystem 画布刷新系统
UGUI源码分析:LayoutSystem布局系统
具体实现
具体实现需要继承LayoutGroup,并实现对应的函数。
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace Game
{
[ExecuteAlways]
public class ZLayoutGroup : LayoutGroup
{
public enum Axis
{
Horizontal = 0,
Vertical = 1
}
public enum CornerHorizontal
{
Left = 0,
Right = 1,
}
public enum CornerVertical
{
Up = 0,
Down = 1,
}
[SerializeField]
protected CornerHorizontal m_StartCornerHorizontal = CornerHorizontal.Left;
[SerializeField]
protected CornerVertical m_StartCornerVertical = CornerVertical.Up;
[SerializeField]
protected Axis m_StartAxis = Axis.Horizontal;
[SerializeField]
protected Vector2 m_CellSize = new Vector2(100,100);
[SerializeField]
protected Vector2 m_Spacing = Vector2.zero;
public Vector2 spacing { get { return m_Spacing; } set { m_Spacing = value; } }
public CornerHorizontal startCornerHorizontal { get { return m_StartCornerHorizontal; } set { m_StartCornerHorizontal = value; } }
public CornerVertical startCornerVertical { get { return m_StartCornerVertical; } set { m_StartCornerVertical = value; } }
public Axis startAxis { get { return m_StartAxis; } set { m_StartAxis = value; } }
public Vector2 cellSize { get { return m_CellSize; } set { m_CellSize = value; } }
public override void CalculateLayoutInputVertical()
{
if (rectChildren.Count == 0) return;
CalcPos();
}
public override void SetLayoutHorizontal()
{
}
public override void SetLayoutVertical()
{
}
protected void CalcPos()
{
Rect rect = rectTransform.rect;
//已知(0,0)点为锚点位置
//去除pading后的大小,如果pading过大会出现问题
Vector2 size = rect.size - new Vector2(padding.horizontal, padding.vertical);
//根据总结得出position为pivot的世界坐标
//rect的xy坐标和position的坐标根据下列计算后为左上角位置,再对应的减去padding掉的就是实际左上角开始的角的位置
Vector2 leftUpPos = rect.size*(Vector2.up-rectTransform.pivot) - new Vector2(-padding.left, padding.top);
//起始点计算左为0右为1,上为0下为-1得到vector2(),乘以size加上右上角的位置
Vector2 startPosCalc = new Vector2((int)startCornerHorizontal, -(int)startCornerVertical);
//左加右减,下加上减
Vector2 childPosCalc = new Vector2((startCornerHorizontal == CornerHorizontal.Left ? 1 : -1), (startCornerVertical == CornerVertical.Down ? 1 : -1));
//起始角的位置,用左上角的位置,使用size来计算出相应的起始点位置
Vector2 startPos = leftUpPos + size * startPosCalc;
//用于计算对应的方向放置物体的数目
Vector2 axis = startAxis == Axis.Horizontal ? Vector2.right : Vector2.up;
//对应布局方向一次可放数目,使用上面得到的计算会将另外一边清零得到想要的边可以放置的数目
int axisCount = (int)(((size + spacing) / (cellSize + spacing)) * axis).magnitude;
for (int i = 0; i < rectChildren.Count; i++)
{
RectTransform child = rectChildren[i];
//设置大小
child.sizeDelta = cellSize;
Vector3 childpos = Vector3.zero;
//计算循环次数(其实是(i+1-1)/(axisCount-1))
int circCount = (i / (axisCount - 1));
//由于是循环往复的,循环的次数是从0开始的,并且index其实也是从0开始的,当反弹时循环次数非2的倍数,需要减去对应的计算结果。
int Index = circCount % 2 == 0 ? (i % (axisCount - 1)) : (axisCount - 1 - (i % (axisCount - 1)));
//由总结得出一个公式
//y上减下加,x左加右减
//而偏移量的计算根据不同布局不同,但是其实只是加减的量调换了一下
//首先是固定需要加减的自身的一半大小
//接着是前面的物体需要的偏移
//一个根据i计算一个根据index计算
childpos = startPos + (cellSize * childPosCalc/2) + (cellSize + spacing) * (startAxis == Axis.Vertical ? new Vector2(i, Index) : new Vector2(Index, i)) * childPosCalc;
child.localPosition = childpos;
}
}
}
}
效果截图
总结
根据源码分析的资料可以得知LayoutGroup中四个函数
public virtual void CalculateLayoutInputHorizontal();
public abstract void CalculateLayoutInputVertical();
public abstract void SetLayoutHorizontal();
public abstract void SetLayoutVertical();
注意点一:上述四个函数其实只是在该UI发生改变时顺序执行,我们只需要将对应的子节点的位置计算出来,并设置其位置,并且在上述四个函数任意一个中实现都可以。
注意点二:请计算子节点的local坐标而不是世界坐标,这是因为在一些项目中可能会重新对UI系统进行一个修改,会导致其位置不在对应的点上,而local坐标是相对父物体的,只要父物体位置没问题那么子物体位置也不会有问题。如何计算local坐标?local坐标其实是相对于父节点的pivot的,pivot左下角为(0,0),右上角为(1,1)所以我们可以利用对应的角比如左上角为(0,1),那么使用size*(Vector2(0,1)-(pivot))
得到左上角相对pivot点的位置。至于size则存储在rect中,获取rect即可。其余计算根据需要的布局解决。
最后
可能理解的不是很到位,希望对看到的人有所帮助。