前言
最近需要做一個需求,是讓 一堆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、理論上可以把圓的曲線擴展成各種奇奇怪怪的曲線,但數學能力低下……