UGUI源代码之Layout—增加MaxWidth和MaxHeight属性

以下内容是根据Unity 2020.1.01f版本进行编写的


1、目的


在工作中遇到的需求,要求滑动列表的节点需要根据格子的数量进行高度自适应,并且有最小高度和最大高度,目前的排序组件只能设置最小高度而无法设置最大高度,于是想看看UGUI的源代码,看看能不能在LayoutElement组件上增加最大高度和最大宽度的属性。

2、参考


本文参考Unity官方的UGUI源代码
Github地址:https://github.com/Unity-Technologies/uGUI

3、重点代码阅读


首先看看LayoutElementEditor类的代码:

SerializedProperty m_IgnoreLayout;
SerializedProperty m_MinWidth;
SerializedProperty m_MinHeight;
SerializedProperty m_PreferredWidth;
SerializedProperty m_PreferredHeight;
SerializedProperty m_FlexibleWidth;
SerializedProperty m_FlexibleHeight;
SerializedProperty m_LayoutPriority;

protected virtual void OnEnable()
{
    m_IgnoreLayout = serializedObject.FindProperty("m_IgnoreLayout");
    m_MinWidth = serializedObject.FindProperty("m_MinWidth");
    m_MinHeight = serializedObject.FindProperty("m_MinHeight");
    m_PreferredWidth = serializedObject.FindProperty("m_PreferredWidth");
    m_PreferredHeight = serializedObject.FindProperty("m_PreferredHeight");
    m_FlexibleWidth = serializedObject.FindProperty("m_FlexibleWidth");
    m_FlexibleHeight = serializedObject.FindProperty("m_FlexibleHeight");
    m_LayoutPriority = serializedObject.FindProperty("m_LayoutPriority");
}

public override void OnInspectorGUI()
{
    serializedObject.Update();

    EditorGUILayout.PropertyField(m_IgnoreLayout);

    if (!m_IgnoreLayout.boolValue)
    {
        EditorGUILayout.Space();

        LayoutElementField(m_MinWidth, 0);
        LayoutElementField(m_MinHeight, 0);
        LayoutElementField(m_PreferredWidth, t => t.rect.width);
        LayoutElementField(m_PreferredHeight, t => t.rect.height);
        LayoutElementField(m_FlexibleWidth, 1);
        LayoutElementField(m_FlexibleHeight, 1);
    }

    EditorGUILayout.PropertyField(m_LayoutPriority);

    serializedObject.ApplyModifiedProperties();
}

这是显示在Inspector面板的属性,实际上就是在显示属性的同时设置其默认值
其中OnEnable函数就是在组件节点显示的时候调用的,用于获取对应的LayoutElement类中的数据

LayoutElement代码(已把注释去掉):

public class LayoutElement : UIBehaviour, ILayoutElement, ILayoutIgnorer
{
    [SerializeField] private bool m_IgnoreLayout = false;
    [SerializeField] private float m_MinWidth = -1;
    [SerializeField] private float m_MinHeight = -1;
    [SerializeField] private float m_PreferredWidth = -1;
    [SerializeField] private float m_PreferredHeight = -1;
    [SerializeField] private float m_FlexibleWidth = -1;
    [SerializeField] private float m_FlexibleHeight = -1;
    [SerializeField] private int m_LayoutPriority = 1;

    public virtual bool ignoreLayout { get { return m_IgnoreLayout; } set { if (SetPropertyUtility.SetStruct(ref m_IgnoreLayout, value)) SetDirty(); } }

    public virtual void CalculateLayoutInputHorizontal() {}
    public virtual void CalculateLayoutInputVertical() {}

    public virtual float minWidth { get { return m_MinWidth; } set { if (SetPropertyUtility.SetStruct(ref m_MinWidth, value)) SetDirty(); } }

    public virtual float minHeight { get { return m_MinHeight; } set { if (SetPropertyUtility.SetStruct(ref m_MinHeight, value)) SetDirty(); } }

    public virtual float preferredWidth { get { return m_PreferredWidth; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredWidth, value)) SetDirty(); } }

    public virtual float preferredHeight { get { return m_PreferredHeight; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredHeight, value)) SetDirty(); } }

    public virtual float flexibleWidth { get { return m_FlexibleWidth; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleWidth, value)) SetDirty(); } }

    public virtual float flexibleHeight { get { return m_FlexibleHeight; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleHeight, value)) SetDirty(); } }

    public virtual int layoutPriority { get { return m_LayoutPriority; } set { if (SetPropertyUtility.SetStruct(ref m_LayoutPriority, value)) SetDirty(); } }


    protected LayoutElement()
    {}

    protected override void OnEnable()
    {
        base.OnEnable();
        SetDirty();
    }

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

    protected override void OnDisable()
    {
        SetDirty();
        base.OnDisable();
    }

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

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

    protected void SetDirty()
    {
        if (!IsActive())
            return;
        LayoutRebuilder.MarkLayoutForRebuild(transform as RectTransform);
    }

#if UNITY_EDITOR
    protected override void OnValidate()
    {
        SetDirty();
    }

#endif
}

这里主要是设置了各种属性,以及在属性值改变时,设置节点大小

再来看看排序组件,这里看HorizontalLayoutGroup的类:

public class HorizontalLayoutGroup : HorizontalOrVerticalLayoutGroup
{
    protected HorizontalLayoutGroup()
    {}

    /// <summary>
    /// Called by the layout system. Also see ILayoutElement
    /// </summary>
    public override void CalculateLayoutInputHorizontal()
    {
        base.CalculateLayoutInputHorizontal();
        CalcAlongAxis(0, false);
    }

    /// <summary>
    /// Called by the layout system. Also see ILayoutElement
    /// </summary>
    public override void CalculateLayoutInputVertical()
    {
        CalcAlongAxis(1, false);
    }

    /// <summary>
    /// Called by the layout system. Also see ILayoutElement
    /// </summary>
    public override void SetLayoutHorizontal()
    {
        SetChildrenAlongAxis(0, false);
    }

    /// <summary>
    /// Called by the layout system. Also see ILayoutElement
    /// </summary>
    public override void SetLayoutVertical()
    {
        SetChildrenAlongAxis(1, false);
    }
}

可以看出,这个类主要是复写计算函数的。

我们实际上是想要在计算最佳尺寸的时候,如果超过了设置的最大值,就返回最大值。看函数名CalcAlongAxis就是计算尺寸的函数,按F12跳转到CalcAlongAxis函数中:

protected void CalcAlongAxis(int axis, bool isVertical)
{
float combinedPadding = (axis == 0 ? padding.horizontal : padding.vertical);
bool controlSize = (axis == 0 ? m_ChildControlWidth : m_ChildControlHeight);
bool useScale = (axis == 0 ? m_ChildScaleWidth : m_ChildScaleHeight);
bool childForceExpandSize = (axis == 0 ? m_ChildForceExpandWidth : m_ChildForceExpandHeight);

float totalMin = combinedPadding;
float totalPreferred = combinedPadding;
float totalFlexible = 0;

bool alongOtherAxis = (isVertical ^ (axis == 1));
for (int i = 0; i < rectChildren.Count; i++)
{
    RectTransform child = rectChildren[i];
    float min, preferred, flexible;
    GetChildSizes(child, axis, controlSize, childForceExpandSize, out min, out preferred, out flexible);

    if (useScale)
    {
        float scaleFactor = child.localScale[axis];
        min *= scaleFactor;
        preferred *= scaleFactor;
        flexible *= scaleFactor;
    }

    if (alongOtherAxis)
    {
        totalMin = Mathf.Max(min + combinedPadding, totalMin);
        totalPreferred = Mathf.Max(preferred + combinedPadding, totalPreferred);
        totalFlexible = Mathf.Max(flexible, totalFlexible);
    }
    else
    {
        totalMin += min + spacing;
        totalPreferred += preferred + spacing;

        // Increment flexible size with element's flexible size.
        totalFlexible += flexible;
    }
}

if (!alongOtherAxis && rectChildren.Count > 0)
{
    totalMin -= spacing;
    totalPreferred -= spacing;
}
totalPreferred = Mathf.Max(totalMin, totalPreferred);
SetLayoutInputForAxis(totalMin, totalPreferred, totalFlexible, axis);
}

可以看出,最佳尺寸preferred是从函数GetChildSizes中获得的,按F12跳转到GetChildSizes函数:

private void GetChildSizes(RectTransform child, int axis, bool controlSize, bool childForceExpand,
    out float min, out float preferred, out float flexible)
{
    if (!controlSize)
    {
        min = child.sizeDelta[axis];
        preferred = min;
        flexible = 0;
    }
    else
    {
        min = LayoutUtility.GetMinSize(child, axis);
        preferred = LayoutUtility.GetPreferredSize(child, axis);
        flexible = LayoutUtility.GetFlexibleSize(child, axis);
    }

    if (childForceExpand)
        flexible = Mathf.Max(flexible, 1);
}

可以看出,最佳尺寸是通过LayoutUtility.GetPreferredSize函数获得的,跳转到GetPreferredSize函数:

public static float GetPreferredSize(RectTransform rect, int axis)
{
    if (axis == 0)
        return GetPreferredWidth(rect);
    return GetPreferredHeight(rect);
}

很明显可以看出,排序组件就是通过GetPreferredWidth和GetPreferredHeight函数获取最佳的宽高的,代码:

public static float GetPreferredWidth(RectTransform rect)
{
    return Mathf.Max(GetLayoutProperty(rect, e => e.minWidth, 0), GetLayoutProperty(rect, e => e.preferredWidth, 0));
}

public static float GetPreferredHeight(RectTransform rect)
{
    return Mathf.Max(GetLayoutProperty(rect, e => e.minHeight, 0), GetLayoutProperty(rect, e => e.preferredHeight, 0));
}

可以看出,这里实际上就是对比最小尺寸和最佳尺寸,取最大值返回,我们只需要修改这里,增加对比最大值属性就可以了
若最小尺寸和最佳尺寸的最大值,比设置的最大值还要大,则返回设置的最大值


4、准备修改UGUI源代码


请看这篇:UGUI源代码之修改源代码的前期准备

已经准备过的同学可以跳过

5、为Layout增加自定义的属性(修改UGUI源代码代码)


首先是LayoutElementEditor类

SerializedProperty m_IgnoreLayout;
SerializedProperty m_MinWidth;
SerializedProperty m_MinHeight;
SerializedProperty m_PreferredWidth;
SerializedProperty m_PreferredHeight;
//增加属性
SerializedProperty m_MaxWidth;
SerializedProperty m_MaxHeight;

SerializedProperty m_FlexibleWidth;
SerializedProperty m_FlexibleHeight;
SerializedProperty m_LayoutPriority;

protected virtual void OnEnable()
{
    m_IgnoreLayout = serializedObject.FindProperty("m_IgnoreLayout");
    m_MinWidth = serializedObject.FindProperty("m_MinWidth");
    m_MinHeight = serializedObject.FindProperty("m_MinHeight");
    m_PreferredWidth = serializedObject.FindProperty("m_PreferredWidth");
m_PreferredHeight = serializedObject.FindProperty("m_PreferredHeight");
//增加属性
    m_MaxWidth = serializedObject.FindProperty("m_MaxWidth");
    m_MaxHeight = serializedObject.FindProperty("m_MaxHeight");

    m_FlexibleWidth = serializedObject.FindProperty("m_FlexibleWidth");
    m_FlexibleHeight = serializedObject.FindProperty("m_FlexibleHeight");
    m_LayoutPriority = serializedObject.FindProperty("m_LayoutPriority");
}

public override void OnInspectorGUI()
{
    serializedObject.Update();

    EditorGUILayout.PropertyField(m_IgnoreLayout);

    if (!m_IgnoreLayout.boolValue)
    {
        EditorGUILayout.Space();

        LayoutElementField(m_MinWidth, 0);
        LayoutElementField(m_MinHeight, 0);
        LayoutElementField(m_PreferredWidth, t => t.rect.width);
        LayoutElementField(m_PreferredHeight, t => t.rect.height);
		 //增加属性
        LayoutElementField(m_MaxWidth, 100);
        LayoutElementField(m_MaxHeight, 100);

        LayoutElementField(m_FlexibleWidth, 1);
        LayoutElementField(m_FlexibleHeight, 1);
    }

    EditorGUILayout.PropertyField(m_LayoutPriority);

    serializedObject.ApplyModifiedProperties();
}

在这个类中增加了两个属性:m_MaxWidth和m_MaxHeight,并完成初始化,默认最大值为100


然后是LayoutElement类(已删除注释):

[SerializeField] private bool m_IgnoreLayout = false;
[SerializeField] private float m_MinWidth = -1;
[SerializeField] private float m_MinHeight = -1;
[SerializeField] private float m_PreferredWidth = -1;
[SerializeField] private float m_PreferredHeight = -1;
//增加属性
[SerializeField] private float m_MaxWidth = -1;
[SerializeField] private float m_MaxHeight = -1;

[SerializeField] private float m_FlexibleWidth = -1;
[SerializeField] private float m_FlexibleHeight = -1;
[SerializeField] private int m_LayoutPriority = 1;

public virtual bool ignoreLayout { get { return m_IgnoreLayout; } set { if (SetPropertyUtility.SetStruct(ref m_IgnoreLayout, value)) SetDirty(); } }

public virtual void CalculateLayoutInputHorizontal() {}
public virtual void CalculateLayoutInputVertical() {}

public virtual float minWidth { get { return m_MinWidth; } set { if (SetPropertyUtility.SetStruct(ref m_MinWidth, value)) SetDirty(); } }

public virtual float minHeight { get { return m_MinHeight; } set { if (SetPropertyUtility.SetStruct(ref m_MinHeight, value)) SetDirty(); } }

public virtual float preferredWidth { get { return m_PreferredWidth; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredWidth, value)) SetDirty(); } }

public virtual float preferredHeight { get { return m_PreferredHeight; } set { if (SetPropertyUtility.SetStruct(ref m_PreferredHeight, value)) SetDirty(); } }

//增加属性
public virtual float maxWidth { get { return m_MaxWidth; } set { if (SetPropertyUtility.SetStruct(ref m_MaxWidth, value)) SetDirty(); } }

//增加属性
public virtual float maxHeight { get { return m_MaxHeight; } set { if (SetPropertyUtility.SetStruct(ref m_MaxHeight, value)) SetDirty(); } }

public virtual float flexibleWidth { get { return m_FlexibleWidth; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleWidth, value)) SetDirty(); } }

public virtual float flexibleHeight { get { return m_FlexibleHeight; } set { if (SetPropertyUtility.SetStruct(ref m_FlexibleHeight, value)) SetDirty(); } }

public virtual int layoutPriority { get { return m_LayoutPriority; } set { if (SetPropertyUtility.SetStruct(ref m_LayoutPriority, value)) SetDirty(); } }


此时在Inspector面板上已经能看到新增的属性了:
在这里插入图片描述
但是此时属性还没有使用,因此,继续增加对此属性的引用

继续修改ILayoutElement类(已删除注释):

public interface ILayoutElement
{
    void CalculateLayoutInputHorizontal();

    void CalculateLayoutInputVertical();

    float minWidth { get; }

    float preferredWidth { get; }

    float flexibleWidth { get; }

    float minHeight { get; }

float preferredHeight { get; }

//增加属性
float maxWidth { get; }

//增加属性
    float maxHeight { get; }

    float flexibleHeight { get; }

    int layoutPriority { get; }
}

在LayoutUtility类中修改获得最佳尺寸的函数:
public static float GetPreferredWidth(RectTransform rect)
{
    float max = Mathf.Max(GetLayoutProperty(rect, e => e.minWidth, 0), GetLayoutProperty(rect, e => e.preferredWidth, 0));
    if (max > GetLayoutProperty(rect, e => e.maxWidth, int.MaxValue))
    {
        return GetLayoutProperty(rect, e => e.maxWidth, int.MaxValue);
    }
    return max;
}

public static float GetPreferredHeight(RectTransform rect)
{
    float max = Mathf.Max(GetLayoutProperty(rect, e => e.minHeight, 0), GetLayoutProperty(rect, e => e.preferredHeight, 0));
    if (max > GetLayoutProperty(rect, e => e.maxHeight, int.MaxValue))
    {
        return GetLayoutProperty(rect, e => e.maxHeight, int.MaxValue);
    }
    return max;
}


在这里只是简单增加了与最大值属性的对比,若最佳尺寸或最小尺寸大于设置的最大尺寸,则直接返回最大尺寸

注意:如果此处设置的最小尺寸比最大尺寸还要大,则会以最大尺寸返回,例如设置minWidth为200,maxWidth为100,则返回的是100

此时再看Unity,会发现有一大堆报错,是因为ILayoutElement被很多类引用到,因此需要每个类都增加其maxWidth和maxHeight属性

其中,LayoutGroup类需要增加以下代码:

public virtual float maxHeight { get { return GetTotalPreferredSize(1); } }
public virtual float maxWidth { get { return GetTotalPreferredSize(0); } }


其它的类(Image、Text、ScrollRect、InputField)只需要把每个类中对应的preferredWidth和preferredHeight属性复制以下,改个名字就行,preferredWidth对应maxWidth,preferredHeight对应maxHeight

6、最终效果

在这里插入图片描述

7、项目工程源代码

https://github.com/CHJ-Self/MyUGUI



大佬们找到问题欢迎拍砖~

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是使用Unity UGUI创建可滚动列表的示例代码: ```csharp using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class MyList : MonoBehaviour { public GameObject listItemPrefab; public Transform contentPanel; public ScrollRect scrollRect; private List<GameObject> listItems = new List<GameObject>(); void Start() { // 创建列表项 for (int i = 0; i < 20; i++) { GameObject listItem = Instantiate(listItemPrefab) as GameObject; listItem.transform.SetParent(contentPanel, false); listItem.GetComponent<Text>().text = "Item " + i; listItems.Add(listItem); } // 计算内容面板大小并设置ScrollRect GridLayoutGroup gridLayout = contentPanel.GetComponent<GridLayoutGroup>(); float cellSize = gridLayout.cellSize.y + gridLayout.spacing.y; float spacing = gridLayout.padding.top + gridLayout.padding.bottom + gridLayout.spacing.y; int rowCount = Mathf.CeilToInt(listItems.Count / (float)gridLayout.constraintCount); float height = rowCount * cellSize + spacing; contentPanel.GetComponent<RectTransform>().sizeDelta = new Vector2(0, height); } } ``` 这里使用了一个名为`listItemPrefab`的预制体作为列表项,`contentPanel`指向Scroll View的内容面板,`scrollRect`则指向Scroll View本身。在`Start()`函数中,首先创建20个列表项并加入到`listItems`列表中,然后根据GridLayoutGroup的设置计算内容面板的大小,并将其设置为`contentPanel`的大小,最后将`contentPanel`的高度设置为计算出的高度。这样,在运行时,就可以在Scroll View中看到可滚动的列表了。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值