Unity_折线图+UI动画

GitHub项目链接点这里(求一个star⭐)

效果

折线图

介绍

实现折线图的大体思路和数学画图一致:先画底部刻度(底部描述),再画点,再连线。

关键字

1. 对象池
2. 迭代
3. DoTween 动画

分析及代码

  • 绘图数据

定义绘图数据的结构GraphData,包含数据的描述信息_desc,数据的值_value。在需要绘图时传递一组GraphData的类型的数据给此模块就可以实现折线图的绘制。

[System.Serializable]
public struct GraphData
{
    public string _desc;
    [Range(0, 100)]
    public float _value;

    public float Rate
    {
        get
        {
            return _value / 100;
        }
    }

    public GraphData(string desc, float value)
    {
        _desc = desc;
        _value = value;
    }
}
  • 1.定义参数

下图是Unity的布局,为了方便管理,在LineGraphPanel中分别设置了LeftSide(左侧刻度),DescContent(底部描述),DotContent(点),LineContent(线)四个空对象存放相应的子元素。

unity布局

	public GraphData[] _datas;//数据
    public float _lineWidth = 3;//线宽
    public float _dotRadius = 2;//点半径
    public Transform _leftSide;//左侧描述
    public RectTransform _descContent;//描述Content
    public RectTransform _dotContent;//点Content
    public RectTransform _lineContent;//线Content
    public Text _descPrefab;//描述Prefab
    public RectTransform _dotPrefab;//点Prefab
    public Image _linePrefab;//线Prefab
    public float _tweenTime = 1f;//动画时间

    //描述、点、线 管理
    private Text[] _descs;
    private RectTransform[] _dots;
    private Image[] _lines;
  • 2.左侧刻度

因为背景、左侧刻度是不会改变的,所以初始化左侧刻度的方法只需要在Start方法中调用一次就可以(背景我直接用的一张10条横线的图片)。

    /// <summary>
    /// 初始化折线图
    /// </summary>
    public void InitLineGraph(GraphData[] data)
    {
        //leftSide
        for (int i = 0; i < _leftSide.childCount; i++)
        {
            _leftSide.GetChild(i).GetComponent<Text>().text = (100 - i * 10).ToString();
        }
    }
  • 3.底部描述

底部描述使用了Unity的自动布局组件,因为在下一步需要绘制时是需要用到这里的位置信息,所以需要在结尾处刷新UI的布局。其次,为了防止动态的刷新数据过于频繁,使用到了对象池的优化(链接)

    /// <summary>
    /// 底部描述
    /// </summary>
    private void DrawDesc()
    {
        _descs = new Text[_datas.Length];
        for (int i = 0; i < _datas.Length; i++)
        {
            Text desc = ObjectPool.Instance.GetObject(_descPrefab.name, _descContent).GetComponent<Text>();
            desc.text = _datas[i]._desc;
            desc.transform.SetAsLastSibling();//使用对象池和自动布局组件会调乱顺序,要重置
            desc.gameObject.SetActive(true);
            _descs[i] = desc;
        }
        LayoutRebuilder.ForceRebuildLayoutImmediate(_descContent);//使用自动布局组件要刷新UI,刷新位置
    }
  • 4.画点

根据底部描述的位置、数据的值计算点的位置

    /// <summary>
    /// 画点
    /// </summary>
    private void DrawDot()
    {
        float height = _dotContent.rect.height;
        _dots = new RectTransform[_datas.Length];
        for (int i = 0; i < _datas.Length; i++)
        {
            RectTransform dot = ObjectPool.Instance.GetObject(_dotPrefab.name, _dotContent).GetComponent<RectTransform>();
            dot.localPosition = new Vector3(_descs[i].transform.localPosition.x, height * (_datas[i].Rate - 0.5f), 0);//锚点在中心
            dot.sizeDelta = Vector2.one * _lineWidth * 2;
            dot.gameObject.SetActive(true);
            _dots[i] = dot;
        }
    }
  • 5.画线

根据上一步的点生成线段,线段我使用的是Image,所以可以随意指定它的宽度,位置、旋转根据相邻的两点计算。这里为了实现动画,使用了DoTween和递归,在每生成完一条线后接着生成下一条。

    /// <summary>
    /// 画线
    /// </summary>
    private void DrawLines()
    {
        _lines = new Image[_datas.Length - 1];
        DrawLine();
    }
    
    /// <summary>
    /// 画线
    /// </summary>
    /// <param name="index"></param>
    private void DrawLine(int index = 0)
    {
        if (index >= _lines.Length) return;
        Vector2 curPos = _dots[index].localPosition;
        Vector2 nextPos = _dots[index + 1].localPosition;
        float length = Vector2.Distance(curPos, nextPos);
        Vector3 dir = curPos - nextPos;
        float angle = Vector3.Angle(Vector3.up, dir);
        Vector2 center = (curPos + nextPos) / 2;
        Image line = ObjectPool.Instance.GetObject(_linePrefab.name, _lineContent).GetComponent<Image>();
        line.rectTransform.localEulerAngles = Vector3.forward * angle;
        line.rectTransform.localPosition = center;
        line.rectTransform.sizeDelta = new Vector2(_lineWidth, length);
        line.gameObject.SetActive(true);
        line.fillAmount = 0;
        line.fillOrigin = dir.x > 0 ? 0 : 1;
        line.DOFillAmount(1, _tweenTime / _lines.Length).OnComplete(() => DrawLine(index + 1));
        _lines[index] = line;
    }
  • 6.整体代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

[System.Serializable]
public struct GraphData
{
    public string _desc;
    [Range(0, 100)]
    public float _value;

    public float Rate
    {
        get
        {
            return _value / 100;
        }
    }

    public GraphData(string desc, float value)
    {
        _desc = desc;
        _value = value;
    }
}

/// <summary>
/// 折线图
/// </summary>
public class UILineGraphManager : MonoBehaviour
{
    public GraphData[] _datas;//数据
    public float _lineWidth = 3;//线宽
    public float _dotRadius = 2;//点半径
    public Transform _leftSide;//左侧描述
    public RectTransform _descContent;//描述Content
    public RectTransform _dotContent;//点Content
    public RectTransform _lineContent;//线Content
    public Text _descPrefab;//描述Prefab
    public RectTransform _dotPrefab;//点Prefab
    public Image _linePrefab;//线Prefab
    public float _tweenTime = 1f;//动画时间

    //描述、点、线 管理
    private Text[] _descs;
    private RectTransform[] _dots;
    private Image[] _lines;

    private void Awake()
    {
        ObjectPool.Instance.SetPrefab(_descPrefab.gameObject);
        ObjectPool.Instance.SetPrefab(_dotPrefab.gameObject);
        ObjectPool.Instance.SetPrefab(_linePrefab.gameObject);
    }

    /// <summary>
    /// 初始化折线图
    /// </summary>
    public void InitLineGraph(GraphData[] data)
    {
        //leftSide
        for (int i = 0; i < _leftSide.childCount; i++)
        {
            _leftSide.GetChild(i).GetComponent<Text>().text = (100 - i * 10).ToString();
        }
        RefeshLineGraph(data);
    }

    /// <summary>
    /// 刷新折线图
    /// </summary>
    public void RefeshLineGraph(GraphData[] data)
    {
        _datas = data;
        ClearTransform(_descContent);
        ClearTransform(_dotContent);
        ClearTransform(_lineContent);
        DrawDesc();
        DrawDot();
        DrawLines();
    }

    /// <summary>
    /// 底部描述
    /// </summary>
    private void DrawDesc()
    {
        _descs = new Text[_datas.Length];
        for (int i = 0; i < _datas.Length; i++)
        {
            Text desc = ObjectPool.Instance.GetObject(_descPrefab.name, _descContent).GetComponent<Text>();
            desc.text = _datas[i]._desc;
            desc.transform.SetAsLastSibling();//使用对象池和自动布局组件会调乱顺序,要重置
            desc.gameObject.SetActive(true);
            _descs[i] = desc;
        }
        LayoutRebuilder.ForceRebuildLayoutImmediate(_descContent);//使用自动布局组件要刷新UI,刷新位置
    }

    /// <summary>
    /// 画点
    /// </summary>
    private void DrawDot()
    {
        float height = _dotContent.rect.height;
        _dots = new RectTransform[_datas.Length];
        for (int i = 0; i < _datas.Length; i++)
        {
            RectTransform dot = ObjectPool.Instance.GetObject(_dotPrefab.name, _dotContent).GetComponent<RectTransform>();
            dot.localPosition = new Vector3(_descs[i].transform.localPosition.x, height * (_datas[i].Rate - 0.5f), 0);//锚点在中心
            dot.sizeDelta = Vector2.one * _lineWidth * 2;
            dot.gameObject.SetActive(true);
            _dots[i] = dot;
        }
    }

    /// <summary>
    /// 画线
    /// </summary>
    private void DrawLines()
    {
        _lines = new Image[_datas.Length - 1];
        DrawLine();
    }

    /// <summary>
    /// 画线
    /// </summary>
    /// <param name="index"></param>
    private void DrawLine(int index = 0)
    {
        if (index >= _lines.Length) return;
        Vector2 curPos = _dots[index].localPosition;
        Vector2 nextPos = _dots[index + 1].localPosition;
        float length = Vector2.Distance(curPos, nextPos);
        Vector3 dir = curPos - nextPos;
        float angle = Vector3.Angle(Vector3.up, dir);
        Vector2 center = (curPos + nextPos) / 2;
        Image line = ObjectPool.Instance.GetObject(_linePrefab.name, _lineContent).GetComponent<Image>();
        line.rectTransform.localEulerAngles = Vector3.forward * angle;
        line.rectTransform.localPosition = center;
        line.rectTransform.sizeDelta = new Vector2(_lineWidth, length);
        line.gameObject.SetActive(true);
        line.fillAmount = 0;
        line.fillOrigin = dir.x > 0 ? 0 : 1;
        line.DOFillAmount(1, _tweenTime / _lines.Length).OnComplete(() => DrawLine(index + 1));
        _lines[index] = line;
    }

    /// <summary>
    /// 入池
    /// </summary>
    /// <param name="parent"></param>
    private void ClearTransform(Transform parent)
    {
        for (int i = 1; i < parent.childCount; i++)
        {
            ObjectPool.Instance.RecycleObj(parent.GetChild(i).gameObject, parent);
        }
    }

}
  • 7.使用示例

初始化时调用InitLineGraph(),刷新数据时调用RefeshLineGraph()。下面是示例:

using UnityEngine;

public class UIGraphManager : MonoBehaviour
{
    public UILineGraphManager _line;
    private GraphData[] _datas;

    private void Start()
    {
        RefeshData();
        _line.InitLineGraph(_datas);
    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.R))
        {
            RefeshData();
            _line.RefeshLineGraph(_datas);
        }
    }

    /// <summary>
    /// 刷新数据
    /// </summary>
    public void RefeshData()
    {
        _datas = new GraphData[8];
        for (int i = 0; i < _datas.Length; i++)
        {
            _datas[i] = new GraphData("数据" + i, Random.Range(0, 100));
        }
    }
}

  • 3
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值