UGUI-实现轮转图

实现原理
  • 位置比例示意图:
    将整个乱转图看成一个圆或椭圆,位置比例为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);
    }
}
  • 关于层级分层和拖动:
  1. 层级
    方法一:
    添加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;**

但是另一个问题:
 &#8195若是向右转动,pos0的层级从3变为1,pos2的层级从0变为2,又会出现遮挡。
解决:
 &#8195除了新层级为最大最小时,增加或减小的层级始终相差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);
    }
}

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值