简介
在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;
}
}