使用UGUI制作界面的时候,经常有使用ContentSizeFitter组件来让父对象适应所有子对象的总大小的情况,而有时候会有需求说,父对象达到某个最大尺寸后,就固定这个大小不再增长了,就是说有个最大尺寸的限制,这要怎么实现呢?话不多说,上代码:
文件:ClampedContentSizeFitter.cs
using UnityEngine;
using UnityEngine.UI;
namespace ZetanStudio.UI
{
public class ClampedContentSizeFitter : ContentSizeFitter
{
#pragma warning disable IDE1006 // 命名样式
[SerializeField] private float m_MaxWidth = -1;
public float maxWdith { get => m_MaxWidth; set { m_MaxWidth = value; SetDirty(); } }
[SerializeField] private float m_MaxHeight = -1;
public float maxHeight { get => m_MaxHeight; set { m_MaxHeight = value; SetDirty(); } }
[System.NonSerialized] private RectTransform m_Rect;
private RectTransform rectTransform
{
get
{
if (m_Rect == null)
m_Rect = GetComponent<RectTransform>();
return m_Rect;
}
}
#pragma warning restore IDE1006 // 命名样式
private DrivenRectTransformTracker m_Tracker;
protected ClampedContentSizeFitter()
{ }
protected override void OnDisable()
{
m_Tracker.Clear();
LayoutRebuilder.MarkLayoutForRebuild(rectTransform);
}
private void HandleSelfFittingAlongAxis(int axis)
{
FitMode fitting = (axis == 0 ? horizontalFit : verticalFit);
if (fitting == FitMode.Unconstrained)
{
m_Tracker.Add(this, rectTransform, DrivenTransformProperties.None);
return;
}
m_Tracker.Add(this, rectTransform, (axis == 0 ? DrivenTransformProperties.SizeDeltaX : DrivenTransformProperties.SizeDeltaY));
var maxValue = axis == 0 ? m_MaxWidth : m_MaxHeight;
if (fitting == FitMode.MinSize)
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, maxValue >= 0 ? Mathf.Min(LayoutUtility.GetMinSize(m_Rect, axis), maxValue) : LayoutUtility.GetMinSize(m_Rect, axis));
else
rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, maxValue >= 0 ? Mathf.Min(LayoutUtility.GetPreferredSize(m_Rect, axis), maxValue) : LayoutUtility.GetPreferredSize(m_Rect, axis));
}
public override void SetLayoutHorizontal()
{
m_Tracker.Clear();
HandleSelfFittingAlongAxis(0);
}
public override void SetLayoutVertical()
{
HandleSelfFittingAlongAxis(1);
}
}
}
相应的检查器界面代码:
文件:ClampedContentSizeFitterInspector.cs(放入任意Editor文件夹中)
using UnityEditor;
using UnityEngine;
namespace ZetanStudio.UI.Editor
{
[CustomEditor(typeof(ClampedContentSizeFitter))]
public class ClampedContentSizeFitterInspector : UnityEditor.UI.ContentSizeFitterEditor
{
SerializedProperty m_HorizontalFit;
SerializedProperty m_VerticalFit;
SerializedProperty m_MaxWidth;
SerializedProperty m_MaxHeight;
protected override void OnEnable()
{
base.OnEnable();
m_HorizontalFit = serializedObject.FindProperty("m_HorizontalFit");
m_VerticalFit = serializedObject.FindProperty("m_VerticalFit");
m_MaxWidth = serializedObject.FindProperty("m_MaxWidth");
m_MaxHeight = serializedObject.FindProperty("m_MaxHeight");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
serializedObject.Update();
if (m_HorizontalFit.enumValueIndex != 0)
LayoutElementField(m_MaxWidth, 0, new GUIContent("最大宽度"));
if (m_VerticalFit.enumValueIndex != 0)
LayoutElementField(m_MaxHeight, 0, new GUIContent("最大高度"));
serializedObject.ApplyModifiedProperties();
}
void LayoutElementField(SerializedProperty property, float defaultValue, GUIContent rawLabel)
{
LayoutElementField(property, _ => defaultValue, rawLabel);
}
void LayoutElementField(SerializedProperty property, System.Func<RectTransform, float> defaultValue, GUIContent rawLabel)
{
Rect position = EditorGUILayout.GetControlRect();
// Label
GUIContent label = EditorGUI.BeginProperty(position, rawLabel, property);
// Rects
Rect fieldPosition = EditorGUI.PrefixLabel(position, label);
Rect toggleRect = fieldPosition;
toggleRect.width = 16;
Rect floatFieldRect = fieldPosition;
floatFieldRect.xMin += 16;
// Checkbox
EditorGUI.BeginChangeCheck();
bool enabled = EditorGUI.ToggleLeft(toggleRect, GUIContent.none, property.floatValue >= 0);
if (EditorGUI.EndChangeCheck())
{
// This could be made better to set all of the targets to their initial width, but mimizing code change for now
property.floatValue = (enabled ? defaultValue((target as ClampedContentSizeFitter).transform as RectTransform) : -1);
}
if (!property.hasMultipleDifferentValues && property.floatValue >= 0)
{
// Float field
EditorGUIUtility.labelWidth = 4; // Small invisible label area for drag zone functionality
EditorGUI.BeginChangeCheck();
float newValue = EditorGUI.FloatField(floatFieldRect, new GUIContent(" "), property.floatValue);
if (EditorGUI.EndChangeCheck())
{
property.floatValue = Mathf.Max(0, newValue);
}
EditorGUIUtility.labelWidth = 0;
}
EditorGUI.EndProperty();
}
}
}