一.在上一篇垂直循环复用滚动列表的基础上,扩展延申了圆形循环复用滚动列表。实现此效果需要导入垂直循环复用滚动列表里面的类。
1.基础类
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.UIElements;
/// <summary>
/// 环形的网格布局;
/// 让子对象摆成一个环形;
/// </summary>
public class CricleGrid : MonoBehaviour
{
/// <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(0f, 360f)]
[SerializeField]
float m_StartAngle = 30;
/// <summary>
/// 起始角度;
/// </summary>
public float StartAngle
{
get { return m_StartAngle; }
set
{
m_StartAngle = value;
IsChanged = true;
}
}
/// <summary>
/// 间隔角度;
/// </summary>
[Range(0f, 360f)]
[SerializeField]
float m_Angle = 30;
/// <summary>
/// 间隔角度;
/// </summary>
public float Angle
{
get { return m_Angle; }
set
{
m_Angle = value;
IsChanged = true;
}
}
public Dictionary<int, CricleScrollItemPosData> itemPosDic = new();
public List<CricleScrollItemPosData> itemPosList = new();
/// <summary>
/// 重新将字节点设置大小;
/// </summary>
public void ResetSizeAndPos()
{
int length = ListRect.Count;
for (int i = 0; i < length; i++)
{
var tran = ListRect[i];
tran.sizeDelta = CellSize;
var v = GerCurPosByIndex(i);
tran.anchoredPosition = new Vector2(v.x,v.y);
tran.localEulerAngles = new Vector3(0,0, v.z);
}
}
/// <summary>
/// 返回第几个子对象应该所在的相对位置;
/// </summary>
public Vector3 GerCurPosByIndex(int index)
{
//1、先计算间隔角度:(弧度制)
float totalAngle = Mathf.Deg2Rad * (index * Angle + m_StartAngle);
//2、计算位置
Vector3 Pos = new Vector2(Radius * Mathf.Cos(totalAngle), Mathf.Sin(totalAngle) * Radius);
Pos.z = index * Angle + m_StartAngle + 180;
return Pos;
}
public ScrollRect scrollRect;
public List<object> list = new();
public GameObject item;
private List<CustomScrollItemMono> scrollTestItems = new();
private int startIndex;
private int endIndex;
private int showItemCount = 10;
private void InitItemsPos()
{
int n = (int)(360f / Angle);
for (int i = 0; i < list.Count; i++)
{
var v = GerCurPosByIndex(i);
CricleScrollItemPosData data = new CricleScrollItemPosData();
data.AnchoredPosition = new Vector3(v.x, v.y);
data.LocalEulerAngles = new Vector3(0, 0, v.z);
itemPosDic.Add(i, data);
itemPosList.Add(data);
//if (i < n)
//{
// var v = GerCurPosByIndex(i);
// CricleScrollItemPosData data = new CricleScrollItemPosData();
// data.AnchoredPosition = new Vector3(v.x, v.y);
// data.LocalEulerAngles = new Vector3(0, 0, v.z);
// itemPosDic.Add(i, data);
// itemPosList.Add(data);
//}
//else
//{
// int temp = i % n;
// int m = (i + 1) / n;
// CricleScrollItemPosData d = new CricleScrollItemPosData();
// d.AnchoredPosition = itemPosDic[temp].AnchoredPosition;
// d.LocalEulerAngles.z += itemPosDic[temp].LocalEulerAngles.z + 360 * m;
// itemPosDic.Add(i, d);
// itemPosList.Add(d);
//}
}
Debug.Log($"InitItemsPos ");
}
private void InitShowItems()
{
for (int i = 0; i < showItemCount; i++)
{
GameObject obj = Instantiate(item, transform);
obj.transform.name = i.ToString();
obj.SetActive(true);
CustomScrollItemMono testItem = obj.GetComponent<CustomScrollItemMono>();
testItem.Init(i, list[i]);
scrollTestItems.Add(testItem);
}
item.SetActive(false);
}
private float eulerAnglersZ;
private float preA = 1;
public ScrollRect ScrollRect;
private RectTransform Content;
/// <summary>
/// 用这个初始化
/// </summary>
void Start()
{
for (int i = 0; i < 100; i++)
{
list.Add(new ScrollTestData() { ID = i });
}
Content = ScrollRect.content;
Content.sizeDelta = new Vector2(500, (100 * Angle / 360 + StartAngle % 360 / 360f) * 2 * Mathf.PI * Radius);
InitItemsPos();
InitShowItems();
RefreshAll();
}
private void Awake()
{
scrollRect.horizontal = false;
scrollRect.vertical = true;
scrollRect.onValueChanged.AddListener((value) =>
{
float offset = Mathf.Abs(preA - value.y);
float aa = (offset) * ((100 - 6) * Angle);
if (scrollRect.velocity.y > 0) //手指上滑
{
transform.localEulerAngles -= new Vector3(0,0, aa);
eulerAnglersZ += aa;
}
else if (scrollRect.velocity.y < 0)//手指下滑
{
transform.localEulerAngles += new Vector3(0, 0, aa);
eulerAnglersZ -= aa;
}
preA = value.y;
for (int i = startIndex; i < itemPosList.Count; i++)
{
if (i + 1 < itemPosList.Count)
{
if (scrollRect.velocity.y > 0)//手指上滑
{
var targetY = itemPosList[i + 1].LocalEulerAngles.z - 180;
//Debug.Log($"ccc y {y} targetY {targetY} startIndex {startIndex} Z {transform.localEulerAngles.z}");
if (eulerAnglersZ + StartAngle >= targetY)
{
startIndex = i + 1;
endIndex = startIndex + showItemCount - 1;
break;
}
}
else if (scrollRect.velocity.y < 0)//手指下滑
{
if (startIndex > 0 && startIndex < itemPosDic.Count)
{
var targetY = itemPosDic[startIndex].LocalEulerAngles.z - 180;
if (eulerAnglersZ + StartAngle <= targetY)
{
startIndex = i - 1;
endIndex = startIndex + showItemCount - 1;
break;
}
}
}
}
}
//Debug.Log($"bbb startIndex {startIndex} endIndex {endIndex}");
if (startIndex > 100 - showItemCount)
{
startIndex = 100 - showItemCount;
}
if (endIndex >= itemPosDic.Count) { return; }
int index = 0;
for (int i = startIndex; i < endIndex + 1; i++)
{
if (index < scrollTestItems.Count && i < itemPosDic.Count)
{
var item = scrollTestItems[index];
item.Init(i, list[i]);
var rect = item.gameObject.GetComponent<RectTransform>();
rect.anchoredPosition3D = itemPosDic[i].AnchoredPosition;
rect.localEulerAngles = itemPosDic[i].LocalEulerAngles;
index += 1;
}
}
});
}
public class CricleScrollItemPosData
{
public Vector3 AnchoredPosition;
public Vector3 LocalEulerAngles;
}
}
2.UI目录
3.item克隆体,挂载脚本
4.按照图示,把CricleGrid脚本,挂在Items节点下,调整各个参数,运行即可。