【Unity】【UGUI】实现围绕一个点的环形布局

前言

最近需要做一个需求,是让 一堆UI控件围城一个圈。大概效果如下图所示:

之前做布局大多是按照方格排成几排,围成一个圈的貌似原生的组件里没有。所以需要自己实现一个,好在不算太难。具体思路很简单:就是设定半径、开始角度、间隔角度以及各个子对象的大小,然后用代码让他们像上图那样摆成一个圈就好了。

 

正文

1、获取子对象

首先第一步就是要获取子对象,这里要注意的是,不是所有的子对象都需要被获取。一般来讲,只需要获取第一级的子对象就好了(子对象的子对象就不需要了)。

代码如下:

 /// <summary>
 /// 当下激活的Rect;
/// </summary>
public List<RectTransform> ListRect = new List<RectTransform>(4);
List<RectTransform> tempListRect = new List<RectTransform>(4);

/// <summary>
/// 获取父节点为本身的子对象
/// </summary>
void GetChidList()
{
    ListRect.Clear();
    GetComponentsInChildren(false, tempListRect);
    int length = tempListRect.Count;
    for (int i = 0; i < length; i++)
    {
        var r = tempListRect[i];
        if (r.transform.parent != transform) continue;
        ListRect.Add(r);
    }
}

 

2、进行环形布局

用简单的三角函数就能实现,代码如下:

/// <summary>
/// 重新将字节点设置大小;
/// </summary>
public void ResetSizeAndPos()
{
    int length = ListRect.Count;
    for (int i = 0; i < length; i++)
    {
        var tran = ListRect[i];       
        tran.anchoredPosition = GerCurPosByIndex(i);
    }
}

/// <summary>
/// 返回第几个子对象应该所在的相对位置;
/// </summary>
public Vector2 GerCurPosByIndex(int index)
{
    //1、先计算间隔角度:(弧度制)
    float totalAngle = Mathf.Deg2Rad * (index * Angle + m_StartAngle);
    //2、计算位置
    Vector2 Pos = new Vector2(Radius * Mathf.Cos(totalAngle), Mathf.Sin(totalAngle) * Radius);
    return Pos;
}

这样就能实现环形布局了, 只要填上半径、起始角度就OK了。

 

3、在什么时候刷新

首先在编辑器下需要每一次发生值变更就刷新一次,或者说就是属性面板每变一次就要刷新一次。因此使用如下代码:

private void OnValidate()
{
    //编辑器下每一次更改需要实时刷新;
    GetChidList();
    ResetSizeAndPos();
}

至于在运行状态下就不好这么操作了,因为此时我们基本不会去动编辑器,而且也不需要实时刷新。一开始我是想通过OnTransformChildrenChanged方法来辅助进行刷新,但是发现这个函数只有以下两个调用时机:

1、在添加个一级子对象时,调用1次;

2、在移除一个一级子对象时,调用3次;

虽然很莫名其妙为什么一个是1次一个是3次,不过暂且放过,因为更关键的一点:在对象被SetActive(ture/false)的时候并不会触发这个函数。这与我们的需求不相符合,因为在对象被关闭时也需要重新布局排列。

所以看来是没有取巧的方法,只有自己手打了。

 

4、最终代码

using System.Collections.Generic;
using UnityEngine;

namespace MyUI
{
    /// <summary>
    /// 环形的网格布局;
    /// 让子对象摆成一个环形;
    /// </summary>
    public class MySimpleCricleGrid : MonoBehaviour 
    {

        /// <summary>
        /// 用这个初始化
        /// </summary>
	    void Start()
        {
            RefreshAll();
        }

        /// <summary>
        /// 是否是自动刷新模式,否则的话需要手动调用刷新;
        /// </summary>
        public bool IsAutoRefresh=true;

        /// <summary>
        /// 是否发生过改变;
        /// </summary>
        private bool IsChanged = false;
        /// <summary>
        /// 上一次检查的数量;
        /// </summary>
        int LastCheckCount = 0;

        /// <summary>
        /// Update每帧调用一次
        /// </summary>
        void Update()
        {
            //检查是否需要自动刷新;
            if (!IsAutoRefresh)
                return;

            if (!IsChanged)
            {
                //检测子物体有没有被改变;
                GetChidList();
                int length = ListRect.Count;
                if (length != LastCheckCount)
                {
                    LastCheckCount = length;
                    IsChanged = true;
                }
                //此时刷新大小和位置;
                if (IsChanged)
                    ResetSizeAndPos();
            }
            else
                RefreshAll();
        }

        private void OnValidate()
        {
            //编辑器下每一次更改需要实时刷新;
            RefreshAll();
        }

        /// <summary>
        /// 全部刷新;
        /// </summary>
        public void RefreshAll()
        {
            GetChidList();
            ResetSizeAndPos();
        }

        /// <summary>
        /// 当下激活的Rect;
        /// </summary>
        public List<RectTransform> ListRect = new List<RectTransform>(4);
        List<RectTransform> tempListRect = new List<RectTransform>(4);

        /// <summary>
        /// 获取父节点为本身的子对象
        /// </summary>
        void GetChidList()
        {
            ListRect.Clear();
            GetComponentsInChildren(false, tempListRect);
            int length = tempListRect.Count;
            for (int i = 0; i < length; i++)
            {
                var r = tempListRect[i];
                if (r.transform.parent != transform) continue;
                ListRect.Add(r);
            }
        }

        /// <summary>
        /// 网格大小;
        /// </summary>
        public Vector2 CellSize = new Vector2();

        /// <summary>
        /// 半径;
        /// </summary>
        public float Radius = 1;

        /// <summary>
        /// 起始角度;
        /// </summary>
        [Range(0, 360)]
        [SerializeField]
        float m_StartAngle = 30;

        /// <summary>
        /// 起始角度;
        /// </summary>
        public float StartAngle
        {
            get { return m_StartAngle; }
            set
            {
                m_StartAngle = value;
                IsChanged = true;
            }
        }

        /// <summary>
        /// 间隔角度;
        /// </summary>
        [Range(0, 360)]
        [SerializeField]
        float m_Angle = 30;

        /// <summary>
        /// 间隔角度;
        /// </summary>
        public float Angle
        {
            get { return m_Angle; }
            set
            {
                m_Angle = value;
                IsChanged = true;
            }
        }

        /// <summary>
        /// 重新将字节点设置大小;
        /// </summary>
        public void ResetSizeAndPos()
        {
            int length = ListRect.Count;
            for (int i = 0; i < length; i++)
            {
                var tran = ListRect[i];
                tran.sizeDelta = CellSize;
                tran.anchoredPosition = GerCurPosByIndex(i);
            }
        }

        /// <summary>
        /// 返回第几个子对象应该所在的相对位置;
        /// </summary>
        public Vector2 GerCurPosByIndex(int index)
        {
            //1、先计算间隔角度:(弧度制)
            float totalAngle = Mathf.Deg2Rad * (index * Angle + m_StartAngle);
            //2、计算位置
            Vector2 Pos = new Vector2(Radius * Mathf.Cos(totalAngle), Mathf.Sin(totalAngle) * Radius);
            return Pos;
        }

    }
}

 

后记

1、总体难度并不大,但是感觉写起来很蠢;

2、我感觉这类东西应该已经有炫酷插件实现了,但并没有发现过。

3、理论上可以把圆的曲线扩展成各种奇奇怪怪的曲线,但数学能力低下……

 

 

 

 

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值