Unity杂文——UI父节点随子节点自适应

  1. 简介
  2. 代码
  3. 需要支持的脚本(源码抄来的SetPropertyUtility)
  4. 编辑器显示Editor代码

原文地址

简介

在UI的开发过程中,经常会遇到Image随子节点的文字变化自动缩放,就是拿Image当背景。笔者遇到这种问题每次都是利用Layout+Content Size Fitter来完成的,笔者想了想每次都要加两个组件,并且Layout只用到了随自己点自适应的功能,于是笔者便想办法把两个功能合成一个脚本来实现需求,于是便有了下面的脚本。

代码

代码如下:

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

/// <summary>
/// 未完成,暂时别用
/// </summary>
[AddComponentMenu("Layout/Rect Transform Fitter", 142)]
[ExecuteAlways]
[RequireComponent(typeof(RectTransform))]
public class RectTransformFit : UIBehaviour, ILayoutGroup
{
    [SerializeField] protected RectTransform m_RectChildren;
    [SerializeField] protected RectOffset m_Padding = new RectOffset();
    [SerializeField] protected TextAnchor m_ChildAlignment = TextAnchor.UpperLeft;
    [SerializeField] protected bool m_ChildControlWidth = false;
    [SerializeField] protected bool m_ChildControlHeight = false;
    [SerializeField] protected ContentSizeFitter.FitMode m_HorizontalFit = ContentSizeFitter.FitMode.Unconstrained;
    [SerializeField] protected ContentSizeFitter.FitMode m_VerticalFit = ContentSizeFitter.FitMode.Unconstrained;

    public RectOffset padding
    {
        get => m_Padding;
        set => SetProperty(ref m_Padding, value);
    }
    public TextAnchor childAlignment { get => m_ChildAlignment;
        set => SetProperty(ref m_ChildAlignment, value);
    }
    public bool childControlWidth
    {
        get => m_ChildControlWidth;
        set => SetProperty(ref m_ChildControlWidth, value);
    }
    public bool childControlHeight
    {
        get => m_ChildControlHeight;
        set => SetProperty(ref m_ChildControlHeight, value);
    }
    public ContentSizeFitter.FitMode horizontalFit
    {
        get => m_HorizontalFit;
        set
        {
            if (SetPropertyUtility.SetStruct(ref m_HorizontalFit, value)) SetDirty();
        }
    }
    public ContentSizeFitter.FitMode verticalFit
    {
        get => m_VerticalFit;
        set
        {
            if (SetPropertyUtility.SetStruct(ref m_VerticalFit, value)) SetDirty();
        }
    }

    [NonSerialized] private RectTransform m_Rect;
    protected RectTransform rectTransform
    {
        get
        {
            if (m_Rect == null)
                m_Rect = GetComponent<RectTransform>();
            return m_Rect;
        }
    }

#pragma warning disable 649
    private DrivenRectTransformTracker m_Tracker;
#pragma warning restore 649

    private void OnEnable()
    {
        m_Rect ??= GetComponent<RectTransform>();
        SetDirty();
    }

    protected override void OnRectTransformDimensionsChange()
    {
        SetDirty();
    }

    protected override void OnDisable()
    {
        m_Tracker.Clear();
        base.OnDisable();
    }

    /// <summary>
    /// Calculate and apply the horizontal component of the size to the RectTransform
    /// </summary>
    public void SetLayoutHorizontal()
    {
        m_Tracker.Clear();
        if (m_RectChildren == null || !m_ChildControlWidth)
        {
            SetDirty();
            return;
        }
        HandleSelfFittingAlongAxis(0, m_RectChildren);
    }

    /// <summary>
    /// Calculate and apply the vertical component of the size to the RectTransform
    /// </summary>
    public void SetLayoutVertical()
    {
        if (m_RectChildren == null || !m_ChildControlHeight)
        {
            SetDirty();
            return;
        }
        HandleSelfFittingAlongAxis(1, m_RectChildren);
    }

    private void HandleSelfFittingAlongAxis(int axis, RectTransform rectChild)
    {
        if (rectChild == null) return;
        var fitting = (axis == 0 ? horizontalFit : verticalFit);
        if (fitting == ContentSizeFitter.FitMode.Unconstrained)
        {
            // Keep a reference to the tracked transform, but don't control its properties:
            m_Tracker.Add(this, rectChild, DrivenTransformProperties.None);
            return;
        }

        m_Tracker.Add(this, rectChild,
            (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));

        // Set size to min or preferred size
        rectChild.SetSizeWithCurrentAnchors((RectTransform.Axis)axis,
            fitting == ContentSizeFitter.FitMode.MinSize
                ? LayoutUtility.GetMinSize(rectChild, axis)
                : LayoutUtility.GetPreferredSize(rectChild, axis));

        SetDirty();
    }

    /// <summary>
    /// Helper method used to set a given property if it has changed.
    /// </summary>
    /// <param name="currentValue">A reference to the member value.</param>
    /// <param name="newValue">The new value.</param>
    protected void SetProperty<T>(ref T currentValue, T newValue)
    {
        if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
            return;
        currentValue = newValue;
        SetDirty();
    }

    protected void SetDirty()
    {
        if (!IsActive())
            return;

        RefreshRect();
        LayoutRebuilder.MarkLayoutForRebuild(m_RectChildren);
        LayoutRebuilder.MarkLayoutForRebuild(m_Rect);
    }

    public void RefreshRect()
    {
        if (m_RectChildren == null) return;
        Vector2 anchoredPos;
        var childSize = m_RectChildren.sizeDelta;
        var width = childSize.x + padding.left + padding.right;
        var height = childSize.y + padding.top + padding.bottom;
        var rectSize = rectTransform.sizeDelta;
        if (horizontalFit != ContentSizeFitter.FitMode.Unconstrained &&
            verticalFit != ContentSizeFitter.FitMode.Unconstrained)
        {
            rectTransform.sizeDelta = new Vector2(width, height);
        }
        else if (horizontalFit != ContentSizeFitter.FitMode.Unconstrained)
        {
            rectTransform.sizeDelta = new Vector2(width, rectSize.y);
        }
        else if (verticalFit != ContentSizeFitter.FitMode.Unconstrained)
        {
            rectTransform.sizeDelta = new Vector2(rectSize.x, height);
        }
        rectSize = rectTransform.sizeDelta;
        var oldPos = rectTransform.anchoredPosition;
        var oldPivot = rectTransform.pivot;

        switch (m_ChildAlignment)
        {
            case TextAnchor.UpperLeft:
                rectTransform.pivot = new Vector2(0, 1);
                anchoredPos = new Vector2(padding.left, -padding.top);
                break;
            case TextAnchor.UpperCenter:
                rectTransform.pivot = new Vector2(0.5f, 1);
                anchoredPos = new Vector2(0, -padding.top);
                break;
            case TextAnchor.UpperRight:
                rectTransform.pivot = new Vector2(1, 1);
                anchoredPos = new Vector2(-padding.right, -padding.top);
                break;
            case TextAnchor.MiddleLeft:
                rectTransform.pivot = new Vector2(0, 0.5f);
                anchoredPos = new Vector2(padding.left, 0);
                break;
            case TextAnchor.MiddleCenter:
                rectTransform.pivot = new Vector2(0.5f, 0);
                anchoredPos = new Vector2(0, 0);
                break;
            case TextAnchor.MiddleRight:
                rectTransform.pivot = new Vector2(1, 0);
                anchoredPos = new Vector2(-padding.right, 0);
                break;
            case TextAnchor.LowerLeft:
                rectTransform.pivot = new Vector2(0, 0);
                anchoredPos = new Vector2(padding.left, padding.bottom);
                break;
            case TextAnchor.LowerCenter:
                rectTransform.pivot = new Vector2(0.5f, 0);
                anchoredPos = new Vector2(0, padding.bottom);
                break;
            case TextAnchor.LowerRight:
                rectTransform.pivot = new Vector2(1, 0);
                anchoredPos = new Vector2(-padding.right, padding.bottom);
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        var pivot = rectTransform.pivot;
        rectTransform.anchoredPosition = new Vector2(oldPos.x + rectSize.x * (pivot.x - oldPivot.x),
            oldPos.y + rectSize.y * (pivot.y - oldPivot.y));
        m_RectChildren.anchorMax = pivot;
        m_RectChildren.anchorMin = pivot;
        m_RectChildren.pivot = pivot;
        m_RectChildren.anchoredPosition = anchoredPos;
    }

#if UNITY_EDITOR

    protected override void OnValidate()
    {
        SetDirty();
    }

#endif
}

需要支持的脚本(源码抄来的SetPropertyUtility)

using System;
using System.Collections.Generic;
using UnityEngine;

internal static class SetPropertyUtility
{
    private const float Tolerance = 0.000001f;                 //通过此值判断值是否发生变化

    public static bool SetColor(ref Color currentValue, Color newValue)
    {
        if (Math.Abs(currentValue.r - newValue.r) < Tolerance &&
            Math.Abs(currentValue.g - newValue.g) < Tolerance &&
            Math.Abs(currentValue.b - newValue.b) < Tolerance &&
            Math.Abs(currentValue.a - newValue.a) < Tolerance)
            return false;

        currentValue = newValue;
        return true;
    }

    public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct
    {
        if (EqualityComparer<T>.Default.Equals(currentValue, newValue))
            return false;

        currentValue = newValue;
        return true;
    }

    public static bool SetClass<T>(ref T currentValue, T newValue) where T : class
    {
        if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue)))
            return false;

        currentValue = newValue;
        return true;
    }
}

编辑器显示Editor代码

代码如下:

using System;
using UnityEditor;
using UnityEditor.UI;
using UnityEngine;

[CustomEditor(typeof(RectTransformFit))]
public class RectTransformFitEditor : SelfControllerEditor
{

    SerializedProperty m_Padding;
    SerializedProperty m_ChildAlignment;
    SerializedProperty m_RectChildren;
    SerializedProperty m_HorizontalFit;
    SerializedProperty m_VerticalFit;
    SerializedProperty m_ChildControlWidth;
    SerializedProperty m_ChildControlHeight;

    protected void OnEnable()
    {
        m_Padding = serializedObject.FindProperty("m_Padding");
        m_ChildAlignment = serializedObject.FindProperty("m_ChildAlignment");
        m_RectChildren = serializedObject.FindProperty("m_RectChildren");
        m_ChildControlWidth = serializedObject.FindProperty("m_ChildControlWidth");
        m_ChildControlHeight = serializedObject.FindProperty("m_ChildControlHeight");
        m_HorizontalFit = serializedObject.FindProperty("m_HorizontalFit");
        m_VerticalFit = serializedObject.FindProperty("m_VerticalFit");
    }


    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        EditorGUILayout.PropertyField(m_Padding, true);
        EditorGUILayout.PropertyField(m_ChildAlignment, true);
        EditorGUILayout.PropertyField(m_RectChildren, true);

        Rect rect = EditorGUILayout.GetControlRect();
        rect = EditorGUI.PrefixLabel(rect, -1, EditorGUIUtility.TrTextContent("Control Child Size"));
        rect.width = Mathf.Max(50, (rect.width - 4) / 3);
        EditorGUIUtility.labelWidth = 50;
        ToggleLeft(rect, m_ChildControlWidth, EditorGUIUtility.TrTextContent("Width"));
        rect.x += rect.width + 2;
        ToggleLeft(rect, m_ChildControlHeight, EditorGUIUtility.TrTextContent("Height"));
        EditorGUIUtility.labelWidth = 0;

        EditorGUILayout.PropertyField(m_HorizontalFit, true);
        EditorGUILayout.PropertyField(m_VerticalFit, true);
        serializedObject.ApplyModifiedProperties();
    }

    void ToggleLeft(Rect position, SerializedProperty property, GUIContent label)
    {
        bool toggle = property.boolValue;
        EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
        EditorGUI.BeginChangeCheck();
        int oldIndent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;
        toggle = EditorGUI.ToggleLeft(position, label, toggle);
        EditorGUI.indentLevel = oldIndent;
        if (EditorGUI.EndChangeCheck())
        {
            property.boolValue = property.hasMultipleDifferentValues || !property.boolValue;
        }
        EditorGUI.showMixedValue = false;
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 841774407@qq.com

×

喜欢就点赞,疼爱就打赏