美文网首页
[Unity3D]自定义ContentSizeFitter

[Unity3D]自定义ContentSizeFitter

作者: 煎蛋的少年 | 来源:发表于2020-04-14 19:39 被阅读0次

    最近在做项目的时候,遇到了一个需求,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

    相关文章

      网友评论

          本文标题:[Unity3D]自定义ContentSizeFitter

          本文链接:https://www.haomeiwen.com/subject/iqzgmhtx.html