One of the other UGUI (Layout and Fitter)

        Exploring the UGUI Kernel (7) In Graphic, we mentioned that the Graphic component SetLayoutDirty will notify LayoutRebuilder that the layout needs to be rebuilt, so how is the layout rebuilt specifically? We know that UGUI has three layout groups, HorizontalLayoutGroup (horizontal layout group), VerticalLayoutGroup (vertical layout group) and GridLayoutGroup (grid layout group). By adding a certain layout group to an object, its sub-objects can be arranged according to the corresponding layout method. So what is the relationship between these LayoutGroups and Graphics? This article will explore the principles of Layout series components with these questions in mind.
        Before studying the Layout series components, we must first take a look at the LayoutRebuilder class. Graphic uses the static method MarkLayoutForRebuild to mark itself for reconstruction.

        public static void MarkLayoutForRebuild(RectTransform rect)
        {
            if (rect == null)
                return;
 
            RectTransform layoutRoot = rect;
            while (true)
            {
                var parent = layoutRoot.parent as RectTransform;
                if (!ValidLayoutGroup(parent))
                    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))
                return;
 
            MarkLayoutRootForRebuild(layoutRoot);
        }


        In this method, the topmost ILayoutGroup type component (ValidLayoutGroup) layoutRoot in the parent object will be found. If not found, it will determine whether it is an ILayoutController type (ValidController), and then create a Rebuilder for the layoutRoot that meets the conditions.

        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);
        }

        s_Rebuilders is an ObjectPool of LayoutRebuilder type. An instance rebuilder is created (or taken out from the Pool) through the Get method (and then this memory is recycled through the Release method). We see that this code calls TryRegisterCanvasElementForLayoutRebuild and registers the rebuilder to CanvasUpdateRegistry.
LayoutRebuilder inherits the ICanvasElement interface. UGUI Kernel Exploration (6) CanvasUpdateRegistry introduced that CanvasUpdateRegistry will be called before Canvas is drawn, and the latter will traverse all components of the ICanvasElement type registered to it and call their Rebuild method.

        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;
            }
        }

        PerformLayoutCalculation gets the ILayoutElement type component from m_ToRebuild (that is, the layoutRoot saved in the rebuilder), and calls their corresponding method (CalculateLayoutInputHorizontal or CalculateLayoutInputVertical) based on the second parameter (Lamda expression). Before calling the method again, call PerformLayoutCalculation recursively with the sub-object of m_ToRebuild as a parameter. (From this we know that Graphic actually calls the MarkLayoutForRebuild method for Image and Text)
        PerformLayoutControl gets the ILayoutController (ILayoutGroup inherits from this interface) component from m_ToRebuild, and calls their corresponding method according to the second parameter (SetLayoutHorizontal or SetLayoutVertical), and then call PerformLayoutControl recursively with the sub-object of m_ToRebuild as a parameter.

        In PerformLayoutControl, we see that components of type ILayoutSelfController (derived interface of ILayoutController) will call methods before other components. So what kind of component is ILayoutSelfController?

In the picture above, we added the ContentSizeFitter component to a Text and set both Horizontal Fit and Vertical Fit to Preferred Size. You can see that the size of this object becomes the same size as the Text content.

ContentSizeFitter is a component that inherits the ILayoutSelfController interface, and AspectRatioFitter also inherits the latter. Both are components that adjust their own size, so they are ILayoutSelfController.

In ContentSizeFitter, the SetLayoutHorizontal and SetLayoutVertical methods will call the HandleSelfFittingAlongAxis method:

        private void HandleSelfFittingAlongAxis(int axis)
        {
            FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
            if (fitting == FitMode.Unconstrained)
                return;
 
            m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
 
            // Set size to min or preferred size
            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));
        }

Set the size (minimum size or preferred size) for the rectTransform according to how it adapts.


AspectRatioFitter is an aspect ratio adapter. Although its SetLayoutHorizontal and SetLayoutVertical methods are empty methods, SetDirty will be called when aspectMode, aspectRatioaspectRatio changes and OnEnable (see Untiy3D component tips (1) OnEnabled and OnDisabled for calling timing), and then UpdateRect:

        private void UpdateRect()
        {
            if (!IsActive())
                return;
 
            m_Tracker.Clear();
 
            switch (m_AspectMode)
            {
#if UNITY_EDITOR
                case AspectMode.None:
                {
                    if (!Application.isPlaying)
                        m_AspectRatio = Mathf.Clamp(rectTransform.rect.width / rectTransform.rect.height, 0.001f, 1000f);
 
                    break;
                }
#endif
                case AspectMode.HeightControlsWidth:
                {
                    m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaX);
                    rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, rectTransform.rect.height * m_AspectRatio);
                    break;
                }
                case AspectMode.WidthControlsHeight:
                {
                    m_Tracker.Add(this, rectTransform, DrivenTransformProperties.SizeDeltaY);
                    rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, rectTransform.rect.width / m_AspectRatio);
                    break;
                }
                case AspectMode.FitInParent:
                case AspectMode.EnvelopeParent:
                {
                    m_Tracker.Add(this, rectTransform,
                        DrivenTransformProperties.Anchors |
                        DrivenTransformProperties.AnchoredPosition |
                        DrivenTransformProperties.SizeDeltaX |
                        DrivenTransformProperties.SizeDeltaY);
 
                    rectTransform.anchorMin = Vector2.zero;
                    rectTransform.anchorMax = Vector2.one;
                    rectTransform.anchoredPosition = Vector2.zero;
 
                    Vector2 sizeDelta = Vector2.zero;
                    Vector2 parentSize = GetParentSize();
                    if ((parentSize.y * aspectRatio < parentSize.x) ^ (m_AspectMode == AspectMode.FitInParent))
                    {
                        sizeDelta.y = GetSizeDeltaToProduceSize(parentSize.x / aspectRatio, 1);
                    }
                    else
                    {
                        sizeDelta.x = GetSizeDeltaToProduceSize(parentSize.y * aspectRatio, 0);
                    }
                    rectTransform.sizeDelta = sizeDelta;
 
                    break;
                }
            }
        }


        Although there is an unknown object m_Tracker, it does not prevent us from reading the code.
        We won’t talk much about HeightControlsWidth (height controls width) and WidthControlsHeight (width controls height). FitInParent means that when aspectRatio is less than the width-to-height ratio of the parent object, the width is set according to aspectRatio based on the height of the parent object. When it is greater than the width of the parent object, the height is set according to aspectRatio. FitInParent is the opposite.

        ​ ​ ​ Next we look at another derived interface of ILayoutController, ILayoutGroup. LayoutGroup inherits ILayoutGroup.

        ​​​​ LayoutGroup is an abstract class from which HorizontalOrVerticalLayoutGroup and GridLayoutGroup inherit. HorizontalOrVerticalLayoutGroup is also an abstract class, and HorizontalLayoutGroup and VerticalLayoutGroup inherit from it.

        LayoutGroup not only inherits ILayoutGroup, but also inherits ILayoutElement, which shows that it can also be added as a layout element to other layout groups or adapters.

    public interface ILayoutElement
    {
        // After this method is invoked, layout horizontal input properties should return up-to-date values.
        // Children will already have up-to-date layout horizontal inputs when this methods is called.
        void CalculateLayoutInputHorizontal();
        // After this method is invoked, layout vertical input properties should return up-to-date values.
        // Children will already have up-to-date layout vertical inputs when this methods is called.
        void CalculateLayoutInputVertical();
 
        // Layout horizontal inputs
        float minWidth { get; }
        float preferredWidth { get; }
        float flexibleWidth { get; }
        // Layout vertical inputs
        float minHeight { get; }
        float preferredHeight { get; }
        float flexibleHeight { get; }
 
        int layoutPriority { get; }
    }

        Let’s first talk about the methods inherited from ILayoutGroup. SetLayoutHorizontal and SetLayoutVertical of LayoutGroup are abstract methods, which are specifically implemented in GridLayoutGroup, HorizontalLayoutGroup and VerticalLayoutGroup.
GridLayoutGroup's SetLayoutHorizontal and SetLayoutVertical call the SetCellsAlongAxis method, which sets the size and position of the sub-objects it contains based on parameters such as CellSize and Spacing.

        ​​  The SetLayoutHorizontal and SetLayoutVertical of the HorizontalLayoutGroup and VerticalLayoutGroup components call SetChildrenAlongAxis of the base class HorizontalOrVerticalLayoutGroup, which will divide it into N parts horizontally or vertically according to its own size (N is the number of sub-objects), and set the position and size of the sub-object.

        The rectChildren (rectangular sub-object) used by SetCellsAlongAxis and SetChildrenAlongAxis is obtained through CalculateLayoutInputHorizontal. This method is inherited from ILayoutElement, mentioned earlier.

        This method saves the RectTransform in the child object that does not ignore Layout in rectChildren.

        The subclasses of LayoutGroup each override CalculateLayoutInputHorizontal and CalculateLayoutInputVertical. In addition to the above operations, they also calculate the minWidth, preferredWidth, flexibleWidth, minHeight, preferredHeight and flexibleHeight properties, and these properties are also properties of the ILayoutElement interface, and these properties are in HorizontalOrVerticalLayoutGroup Used when calculating sub-object dimensions.

Guess you like

Origin blog.csdn.net/ttod/article/details/135016444