UGUI源码解读-布局系统

Posted maplejaw_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UGUI源码解读-布局系统相关的知识,希望对你有一定的参考价值。

CanvasUpdate

枚举类:CanvasUpdate,用于表示当前的布局阶段

public enum CanvasUpdate
{
    /// <summary>
    /// Called before layout.
    /// </summary>
Prelayout= 0,
    /// <summary>
    /// Called for layout.
    /// </summary>
Layout= 1,
    /// <summary>
    /// Called after layout.
    /// </summary>
PostLayout= 2,
    /// <summary>
    /// Called before rendering.
    /// </summary>
PreRender= 3,
    /// <summary>
    /// Called late, before render.
    /// </summary>
LatePreRender= 4,
    /// <summary>
    /// Max enum value. Always last.
    /// </summary>
MaxUpdateValue= 5
}

ICanvasElement

  • 重建方法: void Rebuild(CanvasUpdate executing);
  • 布局重建完成: void LayoutComplete();
  • 图像重建完成: void GraphicUpdateComplete();
  • 检查Element是否无效:bool IsDestroyed();

CanvasUpdateRegistry

内部管理了两个队列m_LayoutRebuildQueue和m_GraphicRebuildQueue。

private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();
private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();

构造函数中注册了画布渲染监听

Canvas.willRenderCanvases += PerformUpdate;

PerformUpdate简化后的逻辑如下,依次处理了m_LayoutRebuildQueue和m_GraphicRebuildQueue两个队列。

private void PerformUpdate()
        {
           
            //处理m_LayoutRebuildQueue队列
            m_PerformingLayoutUpdate = true;
            var layoutRebuildQueueCount = m_LayoutRebuildQueue.Count;
            for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++)
            {
                for (int j = 0; j < layoutRebuildQueueCount; j++)
                {
                    var rebuild = m_LayoutRebuildQueue[j];
                    rebuild.Rebuild((CanvasUpdate)i);
                }
            }

            for (int i = 0; i < layoutRebuildQueueCount; ++i)
                m_LayoutRebuildQueue[i].LayoutComplete();

            m_LayoutRebuildQueue.Clear();
            m_PerformingLayoutUpdate = false;
          

            //处理裁切相关
            ClipperRegistry.instance.Cull();
          
            //处理m_GraphicRebuildQueue队列
            m_PerformingGraphicUpdate = true;
            var graphicRebuildQueueCount = m_GraphicRebuildQueue.Count;
            for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++)
            {
                for (var k = 0; k < graphicRebuildQueueCount; k++)
                {
                    var element = m_GraphicRebuildQueue[k];
                    element.Rebuild((CanvasUpdate)i);
                }
            }

            for (int i = 0; i < graphicRebuildQueueCount; ++i)
                m_GraphicRebuildQueue[i].GraphicUpdateComplete();

            m_GraphicRebuildQueue.Clear();
            m_PerformingGraphicUpdate = false;
        }

m_LayoutRebuildQueue是在哪里注册的呢?经过追述源码,找到了如下函数,该函数在LayoutRebuilder#MarkLayoutRootForRebuild中被调用

//CanvasUpdateRegistry
public static bool TryRegisterCanvasElementForLayoutRebuild(ICanvasElement element)
{
    return instance.InternalRegisterCanvasElementForLayoutRebuild(element);
}

//LayoutRebuilder#MarkLayoutRootForRebuild
private static void MarkLayoutRootForRebuild(RectTransform controller)
{
            if (controller == null)
                return;

            var rebuilder = s_Rebuilders.Get();
            rebuilder.Initialize(controller);
            if (!CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder))
                s_Rebuilders.Release(rebuilder);
}

LayoutRebuilder

布局重建者,实现了ICanvasElement,通过MarkLayoutRootForRebuild将RectTransform包装成ICanvasElement向CanvasUpdateRegistry注册。Rebuild函数实现如下:先后调用了CalculateLayoutInputHorizontal、SetLayoutHorizontal、CalculateLayoutInputVertical、SetLayoutVertical。

public void Rebuild(CanvasUpdate executing)
{
    switch (executing)
    {
        case CanvasUpdate.Layout:
            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;
    }
}

ILayoutElement

布局元素,布局的接收方,存储有关布局的信息:minWidth、preferredWidth、flexibleWidth等等

public interface ILayoutElement
{

    void CalculateLayoutInputHorizontal();

    void CalculateLayoutInputVertical();
    ...
}

ILayoutController

布局控制接口,布局的实施方,制定布局规则。其中ILayoutSelfController和ILayoutGroup都实现了ILayoutController接口,ILayoutSelfController用于标记控制自己的RectTransform,ILayoutGroup用于标记控制子节点的RectTransform。

public interface ILayoutController
{
    void SetLayoutHorizontal();

    void SetLayoutVertical();
}

ILayoutIgnorer

忽略布局接口,忽略开关开启状态将忽略该物体的布局

LayoutGroup

所有LayoutGroup的基类。以HorizontalLayoutGroup为例,具体实现细节参考HorizontalOrVerticalLayoutGroup。

public class HorizontalLayoutGroup : HorizontalOrVerticalLayoutGroup
{
    protected HorizontalLayoutGroup()
    {}

    public override void CalculateLayoutInputHorizontal()
    {
        base.CalculateLayoutInputHorizontal();
        CalcAlongAxis(0, false);
    }

    public override void CalculateLayoutInputVertical()
    {
        CalcAlongAxis(1, false);
    }

    public override void SetLayoutHorizontal()
    {
        SetChildrenAlongAxis(0, false);
    }

    public override void SetLayoutVertical()
    {
        SetChildrenAlongAxis(1, false);
    }
}

CalcAlongAxis用于计算布局元素属性:

protected void CalcAlongAxis(int axis, bool isVertical)
{
    float combinedPadding = (axis == 0 ? padding.horizontal : 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);

    float totalMin = combinedPadding;
    float totalPreferred = combinedPadding;
    float totalFlexible = 0;

    bool alongOtherAxis = (isVertical ^ (axis == 1));
    var rectChildrenCount = rectChildren.Count;
    for (int i = 0; i < rectChildrenCount; i++)
    {
        RectTransform child = rectChildren[i];
        float min, preferred, flexible;
        GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);

        if (useScale)
        {
            float scaleFactor = child.localScale[axis];
            min *= scaleFactor;
            preferred *= scaleFactor;
            flexible *= scaleFactor;
        }

        if (alongOtherAxis)
        {
            totalMin = Mathf.Max(min + combinedPadding, totalMin);
            totalPreferred = Mathf.Max(preferred + combinedPadding, totalPreferred);
            totalFlexible = Mathf.Max(flexible, totalFlexible);
        }
        else
        {
            totalMin += min + spacing;
            totalPreferred += preferred + spacing;

            // Increment flexible size with element's flexible size.
            totalFlexible += flexible;
        }
    }

    if (!alongOtherAxis && rectChildren.Count > 0)
    {
        totalMin -= spacing;
        totalPreferred -= spacing;
    }
    totalPreferred = Mathf.Max(totalMin, totalPreferred);
    SetLayoutInputForAxis(totalMin, totalPreferred, totalFlexible, axis);
}

SetChildrenAlongAxis用于设置子元素的位置和大小:

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);
    float alignmentOnAxis = GetAlignmentOnAxis(axis);

    bool alongOtherAxis = (isVertical ^ (axis == 1));
    int startIndex = m_ReverseArrangement ? rectChildren.Count - 1 : 0;
    int endIndex = m_ReverseArrangement ? 0 : rectChildren.Count;
    int increment = m_ReverseArrangement ? -1 : 1;
    if (alongOtherAxis)
    {
        float innerSize = size - (axis == 0 ? padding.horizontal : padding.vertical);

        for (int i = startIndex; m_ReverseArrangement ? i >= endIndex : i < endIndex; i += increment)
        {
            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 = startIndex; m_ReverseArrangement ? i >= endIndex : i < endIndex; i += increment)
        {
            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;
        }
    }
}

UML类图如下:(引用自https://blog.csdn.net/qq_28820675/article/details/106245195)
在这里插入图片描述
在这里插入图片描述

以上是关于UGUI源码解读-布局系统的主要内容,如果未能解决你的问题,请参考以下文章

UGUI源码解读-布局系统

UGUI源码解读-布局系统

UGUI 源码解读-EventSystem

UGUI 源码解读-EventSystem

UGUI 源码解读-EventSystem

UGUI 源码解读-EventSystem