[Unity3D]自定义ContentSizeFitter

最近在做项目的时候,遇到了一个需求,TEXT组件的大小需要根据文本内容自适应大小区域。这个解决起来很简单,用一个ContentSizeFitter组件就可以了,不过考虑到这些TEXT文本一般都是配备大小相似的IMAGE作为背景的,以往我的做法就是在IMAGE层再套一个ContentSizeFitter&Horizonal Layout Group来匹配TEXT上的ContentSizeFitter,然后再利用LayoutElement组件给IMAGE这个背景图限制最小的宽度和高度。这次就想能不能自己自定义下类似的功能。

于是先去看了下UGUI的源码里ContentSizeFitter的实现方式,我这个看的是2018.4版本的源码,
Unity开源库地址
UGUI源码GIT地址:https://bitbucket.org/Unity-Technologies/ui
ContentSizeFitter代码很简单,一百多行,很容易就找到了疑似自适应的关键函数

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

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

            // Set size to min or preferred size
            if (fitting == FitMode.MinSize)
                rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetMinSize(m_Rect, axis));
            else
                rectTransform.SetSizeWithCurrentAnchors((RectTransform.Axis)axis, LayoutUtility.GetPreferredSize(m_Rect, axis));
        }

        /// <summary>
        /// Calculate and apply the horizontal component of the size to the RectTransform
        /// </summary>
        public virtual void SetLayoutHorizontal()
        {
            m_Tracker.Clear();
            HandleSelfFittingAlongAxis(0);
        }

        /// <summary>
        /// Calculate and apply the vertical component of the size to the RectTransform
        /// </summary>
        public virtual void SetLayoutVertical()
        {
            HandleSelfFittingAlongAxis(1);
        }

然后想着继续追踪下SetLayoutHorizontalSetLayoutVertical的函数来源,最后在LayoutRebuilder里面找到了相关的调用

        public void Rebuild(CanvasUpdate executing)
        {
            switch (executing)
            {
                case CanvasUpdate.Layout:
                    // It's unfortunate that we'll perform the same GetComponents querys for the tree 2 times,
                    // but each tree have to be fully iterated before going to the next action,
                    // so reusing the results would entail storing results in a Dictionary or similar,
                    // which is probably a bigger overhead than performing GetComponents multiple times.
                    PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());
                    PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());
                    PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());
                    PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());
                    break;
            }
        }

Rebuild的函数我没有很理解,不过感觉应该是UGUI重构整个UIMesh的时候需要调用的函数,里面也可以很清楚的看到,是先调用了SetLayoutHorizontal,后调用SetLayoutVertical,所以如果我们要做跟随适配的话,重写SetLayoutVertical的函数应该就可以了。
再深入源码的话,我自己目前的水平就理解不了了,所以先就此停下了。

看下自定义的代码,非常简单。
MConSizeFiiter.cs

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(MConSizeFitterGroup))]
public class MConSizeFitter : ContentSizeFitter
{
    [System.NonSerialized] MConSizeFitterGroup _group;
    MConSizeFitterGroup Group
    {
        get
        {
            if (_group == null)
            {
                _group = GetComponent<MConSizeFitterGroup>();
            }
            return _group;
        }
    }
    [System.NonSerialized] protected RectTransform _rect;
    protected RectTransform Rect
    {
        get
        {
            if (_rect == null)
            {
                _rect = GetComponent<RectTransform>();
            }
            return _rect;
        }
    }
    public override void SetLayoutVertical()
    {
        base.SetLayoutVertical();
        //Debug.LogError("y1111");
        if (Group != null && Rect!= null)
        {
            var list = Group.items;
            Vector2 result;
            for (int i = 0; i < list.Count; i++)
            {
                result = Rect.sizeDelta + list[i].Pandding;
                if (list[i].IsMinOpen)
                {
                    result.x = Mathf.Max(result.x, list[i].LimitValue.x);
                    result.y = Mathf.Max(result.y, list[i].LimitValue.y);
                }
                list[i].Rect.sizeDelta = result;
            }
        }
    }
}

可以看到上面需求的一个MConSizeFitterGroup的类,其原因是我没办法在正常的工程里面创建新的类继承ContentSizeFitterEditor类,甚至没法引用到UnityEditor.UI的命名空间,只能借用一个辅助的脚本来引用需要跟随适配的Recttransform

using UnityEngine;
using System.Collections.Generic;

public class MConSizeFitterGroup : MonoBehaviour
{
    [Header("受到影响的物体")]
    public List<MConSizeFitterItem> items = new List<MConSizeFitterItem>();
}

最后一个是跟随适配的物体的脚本,没有直接用Recttransform是因为想扩展一些功能,比如说我这里加了一些额外空间,最小值边界。

using UnityEngine;
public class MConSizeFitterItem : MonoBehaviour
{
    [System.NonSerialized] private RectTransform _rect;
    public RectTransform Rect
    {
        get
        {
            if (_rect == null)
            {
                _rect = GetComponent<RectTransform>();
            }
            return _rect;
        }
    }
    [Header("额外空间")] [SerializeField] Vector2 _padding = Vector2.zero;
    public Vector2 Pandding { get { return _padding; } }
    [Header("是否开启最小值限制")] [SerializeField] bool _isMinOpen;
    public bool IsMinOpen { get { return _isMinOpen; } }
    [Header("最小值限制")] [SerializeField] Vector2 _limitValue = Vector2.zero;
    public Vector2 LimitValue { get { return _limitValue; } }
}
123.gif

完成。可以看到有最小值的效果,以及文字和图片始终保留了部分空隙。

2020年4月14日 19:39:25

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容