redhat9i 发表于 2022-4-7 07:25

Unity UGUI Layout 系统解析

原因


每次需要使用UnityUGUI的Layout系统,它总能给你一些意想不到的小问题。然后要去看源码,源码又很零散,导致每次需要重写Layout控件都要重新理解,现将其总结出来,以便以后查阅。
更新逻辑


首先从最常用的也最容易出问题的布局三剑客开始切入,HorizontalLayoutGroup、VerticalLayoutGroup 和GridLayoutGroup。

源码中三剑客都继承自LayoutGroup。

LayoutGroup中布局更新的重点在于SetDirty()函数,可以看到在OnEnable()、OnDisable()、OnDidApplyAnimationProperties()、OnRectTransformDimensionsChange()、OnTransformChildrenChanged()等多个涉及到需要更新布局的时机的函数中都有调用它。

SetDirty()函数调用了UGUI系统更新布局的核心类LayoutRebuilder中的函数MarkLayoutForRebuild。

MarkLayoutForRebuild

分析上述代码可以看出:

当一个RectTransform被标记需要更新时,系统会向祖级变换遍历ILayoutGroup组件找到最顶层的ILayoutGroup组件来更新布局。注意遍历的过程中,一旦发现非ILayoutGroup就会中断。这就是为什么连续两层以上嵌套三剑客组件时,布局经常会乱的原因。

看下ILayoutGroup接口,在UGUI源码中,实现ILayoutGroup接口的只有LayoutGroup(三剑客)和ScrollRect,同时ILayoutGroup接口需要实现ILayoutController。源码中的注释指出,如果一个组件驱动其所有子变换的布局,那么需要实现ILayoutGroup接口,如果驱动其自身的布局,需要实现ILayoutSelfController。其中,另一个布局时常用到的组件ContentSizeFitter就是实现了ILayoutSelfController接口。如果需要重写布局组件,注意实现这两个接口。

继续看LayoutRebuilder.MarkLayoutForRebuild函数的处理流程,在函数末尾,调用了MarkLayoutRootForRebuild。在这个函数中,布局组件首先被包裹进LayoutRebuilder实例中(从s_Rebuilders 对象池中获得的)从而变为ICanvasElement,然后通过调用CanvansUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild函数送到真正被注册更新的地方,CanvasUpdateRegistry。

CanvasUpdateRegistry是个单例,在单例被创建的时候,将自身的更新函数PerformUpdate注册到了Canvas组件预备渲染事件Canvas.willRenderCanvases上,在PerformUpdate中,通过调用所有注册过的ICanvasElement.Rebuild函数完成了将所有注册过的ICanvasElement进行重建的任务。值得一提的是,在重建之前,CanvasUpdateRegistry还对所有注册过的ICanvasElement按照深度(即祖级变换的层数)进行了排序,这使底层布局优先于顶层布局完成更新,从而防止布局混乱(只能防止多级布局之间有间隔层级的布局)的原理。
布局逻辑


那么回看LayoutRebuilder中如何实现ICanvasElement.Rebuild的,这是布局控件真正更新布局时的核心。

LayoutRebuilder_Rebuild
页: [1]
查看完整版本: Unity UGUI Layout 系统解析