前言
最近需要做一个需求,是让 一堆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、理论上可以把圆的曲线扩展成各种奇奇怪怪的曲线,但数学能力低下……