实现原理
- 位置比例示意图:
将整个乱转图看成一个圆或椭圆,位置比例为0到1。
比如:有4个item时,0.25即为四分之一处
- 获取各个位置的X坐标:
pos0、pos2的位置X坐标为0,根据比例和周长来确定每个位置的X坐标
length(周长):(item的宽 + 每个item之间的间距) * item的count
private float GetX(float ratio, float length)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("比例不对");
return 0;
}
if (0 <= ratio && ratio < 0.25f)
{
return length * ratio;
}
else if (0.25f <= ratio && ratio < 0.75f)
{
return length * (0.5f - ratio);
}
else
{
return length * (ratio - 1);
}
}
- 获取各个位置的放大倍数
首先要自定义最大与最小的缩放倍数,pos0的位置为最大缩放,pos2的位置为最小。因为pos0到pos2之间的比例相差0.5f,所以可以确定单位缩放,用单位缩放与位置比例确定每个位置的缩放。
单位放大 = (maxScale - minScale) / 0.5f
private float GetScaleTimes(float ratio,float max,float min)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("缩放比例不对");
return 0;
}
float scaleOffset = (max - min) / 0.5f;//单位缩放
if (ratio < 0.5f)
{
return max - scaleOffset * ratio;
}
else
{
return min + scaleOffset * (ratio - 0.5f);
}
}
- 关于层级分层和拖动:
- 层级
方法一:
添加Canvas组件,勾选 Override Sorting 覆盖父物体对其的层级设置,用Sort Order来设定层级
缺点:增加了draw call
方法二:
使用SetSiblingIndex,
transform.SetSiblingIndex(index);
以每个位置的缩放大小进行排序,越大层级越高。
2. 拖动
拖动使用DoTween动画实现
3. 使用SetSiblingIndex可能出现的问题
当向右拖动时,pos0的item向pos1移动,层级从3变为1。pos2的item向pos3移动,层级从0变为2。拖动开始时,pos2的item层级高于pos0的层级,浮现在最上面。
解决:
使用协程,当移动动画进行到一半时,再使用SetSiblingIndex设置层级
注意:SetSiblingIndex时,如果已存在相同的index时,则另外的index会被改变
实现:
Canvas下创建空物体,以Canvas中心为锚点,坐标为(0),挂载脚本RotationDiagram2D.cs。
RotationDiagram2D.cs:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
//==数组中的结构体的属性不允许修改
//==Linq排序
//==层级问题:SetSiblingIndex时,如果已存在相同的index时,则另外的index会被改变;应该按层级从小到大吧来setData???
public class RotationDiagram2D : MonoBehaviour
{
public Vector2 m_ItemSize;
public Sprite[] m_ItemSprites;
public float ScaleTimesMin;//最小缩放系数
public float ScaleTimesMax;//最大缩放系数
public float m_Offset;//item的间距
private List<RotationDiagramItem> m_Items;//保存从0到最后一个item的引用
private List<ItemPosData> m_ItemPosDataList;//保存从pos0到最后一个位置的缩放和坐标
private List<int> posIdList;//保存
public GameObject m_Prefab;
// Start is called before the first frame update
void Start()
{
m_Items = new List<RotationDiagramItem>();
m_ItemPosDataList = new List<ItemPosData>();
CreateItem();
CalculateData();
SetItemData();
}
// Update is called once per frame
void Update()
{
}
//创建模板
private GameObject CreateTemplate()
{
GameObject template = new GameObject();//创建的GameObject自带transform组件
template.AddComponent<RectTransform>().sizeDelta = m_ItemSize;//改变transform为ui用的RectTransform
template.AddComponent<Image>();
template.AddComponent<RotationDiagramItem>();
return template;
}
private void CreateItem()
{
GameObject template = CreateTemplate();
RotationDiagramItem item;
foreach (Sprite sp in m_ItemSprites)
{
item = Instantiate(template).GetComponent<RotationDiagramItem>();
item.SetParent(transform);
item.SetSprite(sp);
item.AddMoveListener(OnMove);
m_Items.Add(item);
}
//删除模板
Destroy(template);
}
//响应子物体拖动
private void OnMove(float _offsetX)
{
int symbol = _offsetX > 0?1: -1;
//遍历所有item改变posId
foreach (RotationDiagramItem item in m_Items)
{
item.ChangeId(symbol, m_Items.Count);
}
for (int i = 0; i < m_ItemPosDataList.Count; i++)
{
m_Items[i].SetData(m_ItemPosDataList[m_Items[i].m_PosId]);
}
}
private void CalculateData()
{
//周长
float length = (m_ItemSize.x + m_Offset) * m_ItemSprites.Length;
//单位位置比例
float offsetRatio = 1.0f / m_ItemSprites.Length;
posIdList = new List<int>();
for (int i = 0; i < m_ItemSprites.Length; i++)
{
//层级
posIdList.Add(i);
m_Items[i].m_PosId = i;
//缩放
float ratio = i * offsetRatio;
ItemPosData data = new ItemPosData();
data.X = GetX(ratio, length);
data.ScaleTimes = GetScaleTimes(ratio,ScaleTimesMax,ScaleTimesMin);
m_ItemPosDataList.Add(data);
}
//以posId对应的缩放从小到大排序,得到后的posIdList的元素顺序即为层级从小到大
posIdList = posIdList.OrderBy(u => m_ItemPosDataList[u].ScaleTimes).ToList();
//设置每个位置的层级
for (int i = 0; i < posIdList.Count; i++)
{
m_ItemPosDataList[posIdList[i]].Order = i;
}
}
private void SetItemData()
{
for (int i = 0; i < m_ItemPosDataList.Count; i++)
{
m_Items[i].SetData(m_ItemPosDataList[i], m_ItemPosDataList.Count);
}
}
private float GetX(float ratio, float length)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("比例不对");
return 0;
}
if (ratio >= 0 && ratio < 0.25f)
{
return length * ratio;
}
else if (ratio >= 0.25f && ratio < 0.75f)
{
return length * (0.5f - ratio);
}
else
{
return length * (ratio - 1);
}
}
private float GetScaleTimes(float ratio,float max,float min)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("缩放比例不对");
return 0;
}
float scaleOffset = (max - min) / 0.5f;//单位缩放
if (ratio < 0.5f)
{
return max - scaleOffset * ratio;
}
else
{
return min + scaleOffset * (ratio - 0.5f);
}
}
}
public class ItemPosData
{
public float X;
public float ScaleTimes;//缩放系数
public int Order;
}
item脚本RotationDiagramItem.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using UnityEngine.EventSystems;
using DG.Tweening;
using System.Threading;
public class RotationDiagramItem : MonoBehaviour,IDragHandler,IEndDragHandler,IPointerClickHandler
{
public int m_PosId;
public float m_aniTime = 3.0f;
private Action<float> m_MoveAction;//回调
private float m_OffsetX;//移动时的x偏移量
private int m_Order =-1;
private bool isFirst = true;
private Image m_Image;
public Image Image
{
get
{
if (m_Image == null)
{
m_Image = GetComponent<Image>();
}
return m_Image;
}
}
public RectTransform m_RectTransform;
public RectTransform RectTrans
{
get
{
if (m_RectTransform == null)
{
m_RectTransform = GetComponent<RectTransform>();
}
return m_RectTransform;
}
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void SetParent(Transform _parent)
{
transform.SetParent(_parent);
}
public void SetSprite(Sprite _sprite)
{
Image.sprite = _sprite;
}
public void SetData(ItemPosData data)
{
RectTrans.DOAnchorPos(Vector2.right * data.X, m_aniTime);
RectTrans.DOScale(Vector2.one * data.ScaleTimes, m_aniTime);
StartCoroutine(Wait(data.Order));
}
private IEnumerator Wait(int order)
{
yield return new WaitForSeconds(m_aniTime * 0.5f);
RectTrans.SetSiblingIndex(order);
}
public void OnDrag(PointerEventData eventData)
{
//Debug.Log("OnDrag");
m_OffsetX += eventData.delta.x;
}
public void OnEndDrag(PointerEventData eventData)
{
m_MoveAction(m_OffsetX);
m_OffsetX = 0;
}
public void AddMoveListener(Action<float> _onMove)
{
m_MoveAction = _onMove;
}
public void ChangeId(int symbol,int totalItemNum)
{
int id = m_PosId;
id += symbol;
if (id < 0)
{
id += totalItemNum;
}
m_PosId = id % totalItemNum;
//m_oldOrder = symbol;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("cur =" + transform.GetSiblingIndex());
Debug.Log("m_Order =" + m_Order);
}
}
改进
若是不使用协程,而实时用设置层级。
则有以下问题:
如果已存在相同的index时,则原本的index会被改变。比如向左拖动时,原代码中SetItemData的顺序为创建Item的顺序,逆时针。即:
原本:pos0 = 3,pos1 = 1; pos2 = 0, pos3 = 2;
想要:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;
(1)设置pos0位置的item数据
pos0的位置的item层级从3变为2,与pos3位置的item层级相同,则pos3位置的item层级从2变为3。
此时:pos0 = 2,pos1 = 1; pos2 = 0, pos3 = 3;
(2)设置pos1位置的item数据
pos1的位置的item层级从1变为3,与pos3位置的item层级相同,则pos3位置的item层级从3变为2。又与pos0的位置的item层级相同,则pos0位置的item层级从2变为1。
此时**:pos0 = 1,pos1 = 3; pos2 = 0, pos3 = 2;**
(3)设置pos2位置的item数据
pos2的位置的item层级从0变为1,与pos0位置的item层级相同,则pos0位置的item层级从1变为0。
此时:pos0 = 0,pos1 = 3; pos2 = 1, pos3 = 2;
(4)设置pos3位置的item数据
pos3的位置的item层级变为0,与pos0位置的item层级相同,则pos0位置的item层级从0变为1。又与pos2的位置的item层级相同,则pos2位置的item层级从1变为2。
结果:pos0 = 1,pos1 = 3; pos2 = 2, pos3 = 0;
则会出现拖动开始时,pos2位置的item遮挡pos0位置的item。
- 解决:
SetItemData应该按新层级的从小到大顺序。
pos3 = 0 < pos2 = 1 < pos0 = 2 < pos1 = 3
原本:pos0 = 3,pos1 = 1; pos2 = 0, pos3 = 2;
想要:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;
(1)设置pos3位置的item数据
pos3的位置的item层级从2变为0,与pos2位置的item层级相同,则pos2位置的item层级从0变为1。又与pos1的位置的item层级相同,则pos1位置的item层级从1变为2。
此时**:pos0 = 3,pos1 = 2; pos2 = 1, pos3 = 0;**
(2)设置pos2位置的item数据
pos2的位置的item层级从1变为1。
此时**:pos0 = 3,pos1 = 2; pos2 = 1, pos3 = 0;**
(3)设置pos0位置的item数据
pos0的位置的item层级从3变为2,与pos1位置的item层级相同,则pos1位置的item层级从2变为3。
此时**:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;**
(4)设置pos1位置的item数据
pos1的位置的item层级从3变为3。
此时**:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;**
但是另一个问题:
 若是向右转动,pos0的层级从3变为1,pos2的层级从0变为2,又会出现遮挡。
解决:
 除了新层级为最大最小时,增加或减小的层级始终相差1,不会出现相差2的情况。则向右转动时:
SetItemData先后顺序:
pos1 = 0 < pos0 = 1 < pos2 = 2 < pos3 = 3
初始:pos0 = 3,pos1 = 1; pos2 = 0, pos3 = 2;
之前:pos0 = 1,pos1 = 0; pos2 = 2, pos3 = 3;
想要:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;
(1)设置pos1位置的item数据
pos3的位置的item层级从1变为0,与pos2位置的item层级相同,则pos2位置的item层级从0变为1。
此时**:pos0 = 3,pos1 = 0; pos2 = 1, pos3 = 2;**
(2)设置pos0位置的item数据
pos0的位置的item层级从3变为2。与pos3位置的item层级相同,则pos3位置的item层级从2变为3。
此时**:pos0 = 2,pos1 = 0; pos2 = 1, pos3 = 3;**
(3)设置pos2位置的item数据
pos2的位置的item层级从1变为1。
此时**:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;**
(4)设置pos3位置的item数据
pos1的位置的item层级从3变为3。
此时**:pos0 = 2,pos1 = 3; pos2 = 1, pos3 = 0;**
代码:
RotationDiagram2D.cs中更改了 方法OnMove 与SetItemData
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
//==数组中的结构体的属性不允许修改
//==Linq排序
//==层级问题:SetSiblingIndex时,如果已存在相同的index时,则另外的index会被改变;应该按层级从小到大吧来setData???
public class RotationDiagram2D : MonoBehaviour
{
public Vector2 m_ItemSize;
public Sprite[] m_ItemSprites;
public float ScaleTimesMin;//最小缩放系数
public float ScaleTimesMax;//最大缩放系数
public float m_Offset;//item的间距
private List<RotationDiagramItem> m_Items;//保存从0到最后一个item的引用
private List<ItemPosData> m_ItemPosDataList;//保存从pos0到最后一个位置的缩放和坐标
private List<int> posIdList;//保存
public GameObject m_Prefab;
// Start is called before the first frame update
void Start()
{
m_Items = new List<RotationDiagramItem>();
m_ItemPosDataList = new List<ItemPosData>();
CreateItem();
CalculateData();
SetItemData();
}
// Update is called once per frame
void Update()
{
}
//创建模板
private GameObject CreateTemplate()
{
GameObject template = new GameObject();//创建的GameObject自带transform组件
template.AddComponent<RectTransform>().sizeDelta = m_ItemSize;//改变transform为ui用的RectTransform
template.AddComponent<Image>();
//GameObject template = Instantiate(m_Prefab);
template.AddComponent<RotationDiagramItem>();
return template;
}
private void CreateItem()
{
GameObject template = CreateTemplate();
RotationDiagramItem item;
foreach (Sprite sp in m_ItemSprites)
{
item = Instantiate(template).GetComponent<RotationDiagramItem>();
item.SetParent(transform);
item.SetSprite(sp);
item.AddMoveListener(OnMove);
m_Items.Add(item);
}
//删除模板
Destroy(template);
}
//响应子物体拖动
private void OnMove(float _offsetX)
{
int symbol = _offsetX > 0?1: -1;
//遍历所有item改变posId
foreach (RotationDiagramItem item in m_Items)
{
item.ChangeId(symbol, m_Items.Count);
}
SetItemData();
}
private void CalculateData()
{
//周长
float length = (m_ItemSize.x + m_Offset) * m_ItemSprites.Length;
//单位位置比例
float offsetRatio = 1.0f / m_ItemSprites.Length;
posIdList = new List<int>();
for (int i = 0; i < m_ItemSprites.Length; i++)
{
//层级
posIdList.Add(i);
m_Items[i].m_PosId = i;
//缩放
float ratio = i * offsetRatio;
ItemPosData data = new ItemPosData();
data.X = GetX(ratio, length);
data.ScaleTimes = GetScaleTimes(ratio,ScaleTimesMax,ScaleTimesMin);
m_ItemPosDataList.Add(data);
}
//以posId对应的缩放从小到大排序,得到后的posIdList的元素顺序即为层级从小到大
posIdList = posIdList.OrderBy(u => m_ItemPosDataList[u].ScaleTimes).ToList();
//设置每个位置的层级
for (int i = 0; i < posIdList.Count; i++)
{
m_ItemPosDataList[posIdList[i]].Order = i;
}
}
private void SetItemData()
{
for (int i = 0; i < posIdList.Count; i++)
{
int posId = posIdList[i];
for (int j = 0; j < m_Items.Count; j++)
{
if (posId == m_Items[j].m_PosId)
{
m_Items[j].SetData(m_ItemPosDataList[posId], m_ItemPosDataList.Count);
break;
}
}
}
}
private float GetX(float ratio, float length)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("比例不对");
return 0;
}
if (ratio >= 0 && ratio < 0.25f)
{
return length * ratio;
}
else if (ratio >= 0.25f && ratio < 0.75f)
{
return length * (0.5f - ratio);
}
else
{
return length * (ratio - 1);
}
}
private float GetScaleTimes(float ratio,float max,float min)
{
if (ratio > 1 || ratio < 0)
{
Debug.LogError("缩放比例不对");
return 0;
}
float scaleOffset = (max - min) / 0.5f;//单位缩放
if (ratio < 0.5f)
{
return max - scaleOffset * ratio;
}
else
{
return min + scaleOffset * (ratio - 0.5f);
}
}
}
public class ItemPosData
{
public float X;
public float ScaleTimes;//缩放系数
public int Order;
}
RotationDiagramItem .cs中更改了方法SetData
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using UnityEngine.EventSystems;
using DG.Tweening;
using System.Threading;
public class RotationDiagramItem : MonoBehaviour,IDragHandler,IEndDragHandler,IPointerClickHandler
{
public int m_PosId;
public float m_aniTime = 3.0f;
private Action<float> m_MoveAction;//回调
private float m_OffsetX;//移动时的x偏移量
private int m_Order =-1;
private bool isFirst = true;
private Image m_Image;
public Image Image
{
get
{
if (m_Image == null)
{
m_Image = GetComponent<Image>();
}
return m_Image;
}
}
public RectTransform m_RectTransform;
public RectTransform RectTrans
{
get
{
if (m_RectTransform == null)
{
m_RectTransform = GetComponent<RectTransform>();
}
return m_RectTransform;
}
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void SetParent(Transform _parent)
{
transform.SetParent(_parent);
}
public void SetSprite(Sprite _sprite)
{
Image.sprite = _sprite;
}
public void SetData(ItemPosData data)
{
RectTrans.DOAnchorPos(Vector2.right * data.X, m_aniTime);
RectTrans.DOScale(Vector2.one * data.ScaleTimes, m_aniTime);
StartCoroutine(Wait(data.Order));
}
public void SetData(ItemPosData data,int totalNum)
{
RectTrans.DOAnchorPos(Vector2.right * data.X,m_aniTime);
RectTrans.DOScale(Vector2.one * data.ScaleTimes, m_aniTime);
if (m_Order < 0)
{
transform.SetSiblingIndex(data.Order);
m_Order = data.Order;
}
else
{
if (data.Order == totalNum - 1 || data.Order == 0)
{
transform.SetSiblingIndex(data.Order);
m_Order = data.Order;
}
else
{
if (data.Order < m_Order)
{
m_Order--;
}
else
{
m_Order++;
}
transform.SetSiblingIndex(m_Order);
}
}
}
private IEnumerator Wait(int order)
{
yield return new WaitForSeconds(m_aniTime * 0.5f);
RectTrans.SetSiblingIndex(order);
}
public void OnDrag(PointerEventData eventData)
{
//Debug.Log("OnDrag");
m_OffsetX += eventData.delta.x;
}
public void OnEndDrag(PointerEventData eventData)
{
m_MoveAction(m_OffsetX);
m_OffsetX = 0;
}
public void AddMoveListener(Action<float> _onMove)
{
m_MoveAction = _onMove;
}
public void ChangeId(int symbol,int totalItemNum)
{
int id = m_PosId;
id += symbol;
if (id < 0)
{
id += totalItemNum;
}
m_PosId = id % totalItemNum;
//m_oldOrder = symbol;
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("cur =" + transform.GetSiblingIndex());
Debug.Log("m_Order =" + m_Order);
}
}