using System.Collections.Generic; using UnityEngine.Events; namespace UnityEngine.UI { public class LayoutRebuilder : ICanvasElement { // m_ToRebuild是一个ILayoutGroup private RectTransform m_ToRebuild; //There are a few of reasons we need to cache the Hash from the transform: // - This is a ValueType (struct) and .Net calculates Hash from the Value Type fields. // - The key of a Dictionary should have a constant Hash value. // - It's possible for the Transform to get nulled from the Native side. // We use this struct with the IndexedSet container, which uses a dictionary as part of it's implementation // So this struct gets used as a key to a dictionary, so we need to guarantee a constant Hash value. private int m_CachedHashFromTransform; // 一个rebuilder结构的池子 static ObjectPool s_Rebuilders = new ObjectPool(null, x => x.Clear()); private void Initialize(RectTransform controller) { m_ToRebuild = controller; m_CachedHashFromTransform = controller.GetHashCode(); } private void Clear() { m_ToRebuild = null; m_CachedHashFromTransform = 0; } static LayoutRebuilder() { RectTransform.reapplyDrivenProperties += ReapplyDrivenProperties; } static void ReapplyDrivenProperties(RectTransform driven) { MarkLayoutForRebuild(driven); } public Transform transform { get { return m_ToRebuild; }} public bool IsDestroyed() { return m_ToRebuild == null; } static void StripDisabledBehavioursFromList(List components) { components.RemoveAll(e => e is Behaviour && !((Behaviour)e).isActiveAndEnabled); } // 立即重新布局一次,而不用等到帧末尾CanvasUpdateReigstry.PerformUpdate的时候 public static void ForceRebuildLayoutImmediate(RectTransform layoutRoot) { var rebuilder = s_Rebuilders.Get(); rebuilder.Initialize(layoutRoot); rebuilder.Rebuild(CanvasUpdate.Layout); s_Rebuilders.Release(rebuilder); } public void Rebuild(CanvasUpdate executing) { switch (executing) { case CanvasUpdate.Layout: // It's unfortunate that we'll perform the same GetComponents querys for the tree 2 times, // but each tree have to be fully iterated before going to the next action, // so reusing the results would entail storing results in a Dictionary or similar, // which is probably a bigger overhead than performing GetComponents multiple times. 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; } } // 从上到下遍历,执行action private void PerformLayoutControl(RectTransform rect, UnityAction action) { if (rect == null) return; var components = ListPool.Get(); rect.GetComponents(typeof(ILayoutController), components); StripDisabledBehavioursFromList(components); // If there are no controllers on this rect we can skip this entire sub-tree // We don't need to consider controllers on children deeper in the sub-tree either, // since they will be their own roots. if (components.Count > 0) { // // Layout control needs to executed top down with parents being done before their children, // because the children rely on the sizes of the parents. // 做两次遍历,先执行ILayoutSelfController比如ContentSizeFitter,再执行ILayoutGroup // First call layout controllers that may change their own RectTransform for (int i = 0; i < components.Count; i++) if (components[i] is ILayoutSelfController) action(components[i]); // Then call the remaining, such as layout groups that change their children, taking their own RectTransform size into account. for (int i = 0; i < components.Count; i++) if (!(components[i] is ILayoutSelfController)) action(components[i]); for (int i = 0; i < rect.childCount; i++) PerformLayoutControl(rect.GetChild(i) as RectTransform, action); } ListPool.Release(components); } private void PerformLayoutCalculation(RectTransform rect, UnityAction action) { if (rect == null) return; var components = ListPool.Get(); rect.GetComponents(typeof(ILayoutElement), components); StripDisabledBehavioursFromList(components); // If there are no controllers on this rect we can skip this entire sub-tree // We don't need to consider controllers on children deeper in the sub-tree either, // since they will be their own roots. if (components.Count > 0 || rect.GetComponent(typeof(ILayoutGroup))) { // 先从子节点开始,最后到父节点,这样父节点可以得到子节点的信息 // Layout calculations needs to executed bottom up with children being done before their parents, // because the parent calculated sizes rely on the sizes of the children. for (int i = 0; i < rect.childCount; i++) PerformLayoutCalculation(rect.GetChild(i) as RectTransform, action); for (int i = 0; i < components.Count; i++) action(components[i]); } ListPool.Release(components); } // 找到rect祖先节点中的layoutGroup并加入队列 public static void MarkLayoutForRebuild(RectTransform rect) { if (rect == null) return; var comps = ListPool.Get(); RectTransform layoutRoot = rect; // 祖先节点中的layoutGroup while (true) { var parent = layoutRoot.parent as RectTransform; if (!ValidLayoutGroup(parent, comps)) break; layoutRoot = parent; } // We know the layout root is valid if it's not the same as the rect, // since we checked that above. But if they're the same we still need to check. if (layoutRoot == rect && !ValidController(layoutRoot, comps)) { ListPool.Release(comps); return; } MarkLayoutRootForRebuild(layoutRoot); ListPool.Release(comps); } private static bool ValidLayoutGroup(RectTransform parent, List comps) { if (parent == null) return false; parent.GetComponents(typeof(ILayoutGroup), comps); StripDisabledBehavioursFromList(comps); var validCount = comps.Count > 0; return validCount; } private static bool ValidController(RectTransform layoutRoot, List comps) { if (layoutRoot == null) return false; layoutRoot.GetComponents(typeof(ILayoutController), comps); StripDisabledBehavioursFromList(comps); var valid = comps.Count > 0; return valid; } private static void MarkLayoutRootForRebuild(RectTransform controller) { if (controller == null) return; var rebuilder = s_Rebuilders.Get(); rebuilder.Initialize(controller); // 注册到CanvasUpdateRegistry里 if (!CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild(rebuilder)) s_Rebuilders.Release(rebuilder); } public void LayoutComplete() { // 放回池子里 s_Rebuilders.Release(this); } public void GraphicUpdateComplete() {} public override int GetHashCode() { return m_CachedHashFromTransform; } public override bool Equals(object obj) { return obj.GetHashCode() == GetHashCode(); } public override string ToString() { return "(Layout Rebuilder for) " + m_ToRebuild; } } }