UGUI源码分析系列总览
相关前置:
UGUI CanvasUpdateSystem源码分析
UGUI源码分析:LayoutSystem布局系统
UGUI源码分析:LayoutGroup中的纵横布局组件
BaseClass: LayoutGroup
Interface: 间接继承了ILayoutElement, ILayoutGroup
Intro:布局系统中的网格布局组件
GridLayoutGroup,是LayoutGroup衍生的用于网格布局的组件,不同于纵横布局,网格布局中严格要求了对子物体尺寸的设置。
STEP1:依然是延续LayoutRebuilder的Rebuild方法,大致流程如HorizontalOrVerticalLayoutGroup相似(详情),不同点在于纵横组件只针对自身的单一轴进行布局,而网格组件则要涵盖两条轴上的布局逻辑。首先被执行的是ILayoutElement的CalculateLayoutInputHorizontal方法。
public override void CalculateLayoutInputHorizontal()
{
//LayoutGroup 基类方法
base.CalculateLayoutInputHorizontal();
//若对排列有约束限制,则初始化设置参数
//这里是横轴则只获取列数的限制
//CalculateLayoutInputVertical中则会获取minRows
int minColumns = 0;
int preferredColumns = 0;
if (m_Constraint == Constraint.FixedColumnCount)
{
minColumns = preferredColumns = m_ConstraintCount;
}
else if (m_Constraint == Constraint.FixedRowCount)
{
minColumns = preferredColumns = Mathf.CeilToInt(rectChildren.Count / (float)m_ConstraintCount - 0.001f);
}
else
{
minColumns = 1;
preferredColumns = Mathf.CeilToInt(Mathf.Sqrt(rectChildren.Count));
}
//同HorizontalOrVerticalLayoutGroup组件,初始化参数
SetLayoutInputForAxis(
padding.horizontal + (cellSize.x + spacing.x) * minColumns - spacing.x,
padding.horizontal + (cellSize.x + spacing.x) * preferredColumns - spacing.x,
-1, 0);
}
STEP2: GridLayoutGroup的布局实现原理上是与HorizontalOrVerticalLayoutGroup相同的,依靠SetInsetAndSizeFromParentEdge方法实现子物体尺寸与位置的设置。
//执行两条轴的布局
public override void SetLayoutHorizontal()
{
SetCellsAlongAxis(0);
}
public override void SetLayoutVertical()
{
SetCellsAlongAxis(1);
}
这里为了保持阅读舒适度就不贴太长的代码块了,根据设置的参数计算出startOffset(初始位置),cellSize+spacing(尺寸+间隔) 对子物体进行设置。
for (int i = 0; i < rectChildren.Count; i++)
{
...
SetChildAlongAxis(rectChildren[i], 0, startOffset.x + (cellSize[0] + spacing[0]) * positionX, cellSize[0]);
SetChildAlongAxis(rectChildren[i], 1, startOffset.y + (cellSize[1] + spacing[1]) * positionY, cellSize[1]);
}
BaseClass: UIBehaviour
Interface: ILayoutSelfController
Intro:布局系统中尺寸调节组件
ContentSizeFitter,是用于调整组件区域使其自适的组件,一般用于与ScrollRect滑动列表以及纵横布局组件搭配,实现动态数量的滑动列表效果,以及与Text组件一起使用,可以根据文字长短进行区域尺寸的变化。
ContentSizeFitter继承了ILayoutSelfController接口(ILayoutController接口的衍生),和LayoutGroup一样被布局系统所处理。而和LayoutGroup不同的地方在于,ContentSizeFitter不改变子物体的大小和位置,而是根据子物体(ILayoutElement)来改变自身的尺寸。
STEP1:ContentSizeFitter Enable阶段会设置布局标记(脏标记),来触发Rebuild
protected override void OnEnable()
{
base.OnEnable();
SetDirty();
}
protected void SetDirty()
{
...
//封装成LayoutRebuilder等待被重建
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
STEP2:当Canvas触发重建过程,其封装成的LayoutRebuilder执行了Rebuild方法。ContentSizeFitter 自身并没有继承ILayoutElement,所有跳过Rebuild的CalculateLayoutInputHorizontal/Vertical部分,执行了它的SetLayoutHorizontal/Vertical接口方法。
public virtual void SetLayoutHorizontal()
{
...
//根据轴进行尺寸的改变
HandleSelfFittingAlongAxis(0);
}
public virtual void SetLayoutVertical()
{
HandleSelfFittingAlongAxis(1);
}
private void HandleSelfFittingAlongAxis(int axis)
{
//获取目标轴的适应类型
FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
//不强制的类型时不会进行尺寸改变
if (fitting == FitMode.Unconstrained)
{
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
return;
}
//添加Tracker的部分无法被修改
m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
//根据类型选择适应的尺寸
if (fitting == FitMode.MinSize)
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetMinSize(m_Rect, axis));
else
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetPreferredSize(m_Rect, axis));
}
由上述分析,ContentSizeFitter主要依靠LayoutUtility.GetMinSize与LayoutUtility.GetPreferredSize实现尺寸的变化,这两个方法会从物体自身寻找ILayoutElement,从中获取minWidth与preferredWidth,不存在则返回默认值0。
因为ContentSizeFitter自身并未继承ILayoutElement,所以仅仅只有ContentSizeFitter时会将尺寸变为0。
如图所示:当垂直适应被选择为Prefered Size时,因为不存在ILayoutElement组件,所以Height被设置成了默认值0
添加LayoutElement组件,并设置Prefered Size 即可看见变化
.
.
.
.
.
嗨,我是作者Vin129,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)