Android自定义控件 — 图形报表的实现(折线图、曲线图、动态曲线图)(View与SurfaceView分别实现图表控件)

图形报表很常用,因为展示数据比较直观,常见的形式有很多,如:折线图、柱形图、饼图、雷达图、股票图、还有一些3D效果的图表等。
Android中也有不少第三方图表库,但是很难兼容各种各样的需求。
如果第三方库不能满足我们的需要,那么就需要自己去写这么一个控件。

往往在APP需求给定后,很多开发者却无从下手,不知道该如何写。
今天刚好抽出点时间,做了个小Demo,给大家讲解一下。
本节,主要分享自定义图表的基本过程,不会涉及过于复杂的知识点。
咱们还是按照:需求、分析、设计、实现、总结这种方式给大家讲解吧!!!
这样大家也更容易看得懂。


需求

先上效果图:

需求内容:
1.数据:
-- 模拟50天的雾霾数值吧,每天的数值是一个100以内的随机数;
-- 以当前日期为最后一天,向前取50天的数据,也就是50条;
2.业务逻辑
-- 页面加载时,请求数据,展示在图表上;
-- 点击【刷新】数据,重新请求数据,展示在图表上;
3.View
-- 图表背景色为暗灰色:#343643;
-- 图表背景边框线颜色为浅蓝色:#999dd2;
-- 曲线颜色为蓝色:#7176ff;
-- 文字颜色为白色;
-- 图表可设置Padding值;
-- 图表全量显示数据,即适配显示;
-- 曲线上的数值文本显示在对应的位置;
-- X坐标轴左右分别显示 开始和结束的日期,并与左右边框线对齐;
-- 图表应支持两种查看方式:整体加载(全量加载) 和 逐条加载(动态加载)


分析

1.数据比较简单,做个随机数即可,略;
2.业务逻辑,较简单,略;
3.View,本节的重点,需要详细分析一下:
3.1 这种图表控件如何实现?

 

一般做法:使用画布、画笔进行绘制。 
如何绘制:使用画笔在画布上绘制图形
(画布类提供了很多画图的方法,画笔可以设置各种笔触效果)。

建议:大家最好提前了解一下画布和画笔的用法。

3.2 背景色如何绘制?

 

canvas.drawColor(参数:颜色)即可,很简单,即:画布直接填充背景颜色,不用画笔。

3.3 背景边框线如何实现?

 

方案1:先定义路径Path,记录每一个跟边框线的信息,再使用canvas.drawPath进行绘制;
方案2:使用canvas.drawLine分别绘制每一条横线和纵线;

建议:多线条时,canvas.drawPath管理更简单,绘制会更方便一些。

3.4 曲线如何绘制?

 

我们可以看作二维坐标系,包含X轴和Y轴;

 

那么,曲线的数据如何才能在坐标系中合适的显示呢?
其实不难,我们可以根据画布大小(或控件大小(如果画布尺寸等于控件尺寸)),
计算出曲线的每个数据在X轴和Y轴的位置信息,然后将这些位置点连成线就可以了;

 

X轴应显示数据的位置:
以图表能适配全量数据为参考(也就是能显示全部的数据,本Demo中就是50条雾霾数据的点):
X轴的长度应与数据总条数对应,那么每一条数据在X轴的位置,应是:
    每条数据在X轴的间隔 = X轴长度 / 数据条数;
    每条数据在X轴的位置 = 第N条数据 * 间隔;

 

Y轴应显示数据的位置:
以图表能适配全量数据为参考,
Y轴的区域应能包含所有数据大小,那么,我们需要先获得数据的最大最小值与之对应,
每一条数据num在Y轴的位置,应是:
    每条数据的Y轴比率 = (num - min ) / (max - min);
    每条数据在Y轴的位置 = 比率 * Y轴长度;

 

获得了数据在X、Y轴的位置,我们就可以绘制曲线了,
此处仍然使用Path收集每一个数据点的位置,同时使用曲线进行连接,
即path.quadTo(x1, y1,x2,y2)(该方法后面有介绍);
然后再画布上绘制曲线路径:canvas.drawPath(path,paint);

3.5 如何绘制文本?

 

使用canvas.drawText(text, x, y, paint);
不过x,y的位置的计算,稍微麻烦一些,大家可以看一下这篇文章的相关介绍:
https://www.jianshu.com/p/3e48dd0547a0
文章 -- 绘图基础 -- 绘制文本  

文本绘制差异:

 

文本绘制时并非从文本的左上角开始绘制,而是基于Baseline开始绘制。
举例:
如果我们想在自定义控件左上角位置绘制文本,
可能会这么写canvas.drawText("MfgiA", 0, 0, paint);
但是这么写,等运行出来,我们发现该控件左上角只会显示Baseline下面的内容,
也就只能看到字母g的下半部分,
而其他部分,因为超出了自定义控件上边界,所以没有被绘制出来。

如果不明白也不要紧,我们先学习主要的知识。
如果想把文本位置控制的特别精确,请务必参考该文章。

3.6 动态图表如何绘制?
图表的动态效果其实就是每隔一定时间重绘一次,也就是动态了(视频效果也是这么个原理);
之所以做成两种效果(非动态/动态),主要是让大家了解一下View和SurfaceView的用法差异。
主要差异如下:

 

View    
-- 仅能在主线程中刷新。
   缺点:如果绘制内容过多或频率过高,会影响主线程FPS,造成页面卡顿
-- 使用了单缓冲;
缓冲可以理解成对处理的包装,举个简单易懂点的例子:
   工人搬砖
   工人有10000块砖要从A区搬到B区,他每次搬一块,要搬10000次,
   为了不想来回跑这么多次,工人想了个办法,找了个筐来背砖,每筐可以背100块,
   这样他就来回跑100次就行了,提高了搬砖效率。那么,这个筐呢就是一个缓冲处理。

在View的绘制上也很容易理解,例如:我们使用画笔按序(中间可有停顿)绘制多个图形,
但是View并没有一个个的去绘制,而是在一次draw方法中,全部绘制了出来。
因为,View也使用了缓冲处理。

SurfaceView   
-- 可在子线程中刷新;
   如果绘制的内容少,不建议使用,因为创建线程和缓冲区,也增加了内存。
   反之,推荐使用,但是要注意线程的管控。   
-- 使用了双缓冲;
   继续以工人搬砖的例子讲解。
   工人转身忽然看到了一辆卡车(一车能装>1万块),心想这不更省事了么,
   于是他先把一框框砖搬到了车上,再把车开到B区,卸砖。
   这辆车也就相当于第二次缓冲了。

在控件绘制时实现双缓冲一般可以这么做:
1.新建一个临时图片,并创建其临时画布(画布相当于那辆卡车);
2.将我们想绘制的内容,先绘制到临时图片的画布上(即图片上)
3.在控件需要绘制时,再把图片绘制到控件的真正画布上;

 

经过上面的对比分析,我们可以得出结论:
1.全量加载的图表(曲线图),使用View或SurfaceView来绘制都是可以的
  因为:绘制的信息适量,没有特别的性能要求。
2.逐条加载的图表(动态曲线图),我们尽量使用SurfaceView来绘制
  因为:如果在View里使用线程sleep控制逐条加载,会导致主线程阻塞
  (也就是页面看着卡顿半天,等阻塞恢复之后,再忽然绘制出来的效果)。
  如果想不卡顿,只能在View中使用线程或Timer来处理逐条效果,然后再与主线程进行通信。
  与其这么麻烦,我们不如使用SurfaceView,直接能在子线程中刷新View不是更好吗。

看完上面的介绍,相信大家对View与SurfaceView的区别和用法,也应该了解一些了。
那么,咱们开始下一步吧。


设计

这一个功能实现相对复杂一些,我们最好对Demo进行一个简单的分层或模块设计。
分析我们的Demo应有的结构,主要包含

  1. 两种自定义图表控件(View和SurfaceView)、
  2. 一些简单的业务逻辑、
  3. 数据的处理。

那么,咱们直接用现成的框架吧,MVC、MVP都是可以的,不过MVC、MVP用哪个好呢?
我们直接使用MVP吧,解耦比MVC更好一些。
此处就不画架构图了,直接文本表示吧:

M(数据层):

 

1\. IChartData.java 图表数据接口(提供了一个方法:获得图表数据)
2\. ChartDataImpl.java 图表数据实现类(实现了上面的接口)
3\. ChartDataInfo.java 图表数据实体类(封装了两个属性:日期和数值)
4\. ChartDateUtils.java 工具类(主要是日期格式的处理
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值