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源码解读-布局系统的主要内容,如果未能解决你的问题,请参考以下文章