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

原文地址

简介

在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;
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容