【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、理論上可以把圓的曲線擴展成各種奇奇怪怪的曲線,但數學能力低下……

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章