UGUI-实现轮转图

    技术2022-07-11  102

    实现原理
    位置比例示意图: 将整个乱转图看成一个圆或椭圆,位置比例为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); } }
    Processed: 0.033, SQL: 9