最近产品说要在项目中加个图表,虽然Android已经有很多成熟的关于图表的框架,但为了这个项目中一两个图表而多引入一个框架觉得有点浪费,所以就想自己动手写一个。先上图:
好在这个图并没有那么复杂,先对这个进行步骤拆分:
- 准备工作
- 确定动态数据
- 确定原点
- 绘制X轴
- 绘制Y轴
- 绘制曲线
1. 准备工作
定义一个类,让它继承自View、定义Paint、初始化…,这部分就不啰嗦了。
2. 确定动态数据
通常这种XY轴的图表只需要给出一组点,按照点的坐标进行绘制即可。这个图也一样需要X轴和Y轴两个坐标,但这个图表里的X轴表示的是日期,每天都绘制一个点,取最近的30个点。因此在传递过来的数据中对于X轴我们要的并非是具体数值,而是开始和结束时间的文字。因此我们定义如下数据bean:
public static class Data {
public String dataX;
public int dataY;
}
对外提供方法:
public void setDataSource(List<Data> dataSource) {
this.dataSource = dataSource;
invalidate();
}
为了以后的拓展性,Y轴上的"元"设置为动态添加:
/**
* Y坐标的后缀
* @param suffixY
*/
public void setSuffixY(String suffixY) {
this.suffixY = suffixY;
}
3. 确定原点
由于X轴要右对齐,所以以下图标注的位置作为原点
定义两个变量 originX、originY,用来表示原点的XY坐标。下面要对这两个坐标进行计算:
originX=paddingLeft+Y轴text;
originY=View的height-paddingBottom-X轴text;
originY:
mPaint.getTextBounds(txtLast, 0, txtLast.length(), textRec);
originY = height - getPaddingBottom() - textRec.height() - 16;
txtLast表示X轴text的文字,末尾的-16是为了让下面的线和文字间有些间距。
originX与originY类似,只是textRec.height()换成了textRec.width():
mPaint.getTextBounds("10元", 0,"10元".length(), textRec);
originX = getPaddingLeft() + textRec.width() + 16;
4. 绘制X轴
在X轴我们只需要绘制X轴上的左右两个坐标(即图中的“6月1日”、“6月30日”),数据已经通过setData传入直接绘制即可:
//绘制X轴坐标(左右各一个)
canvas.drawText(txtFirst, originX, height - getPaddingBottom(), mPaint);
canvas.drawText(txtLast, width - getPaddingRight() - textWidthX,height - getPaddingBottom(), mPaint);
5. 绘制Y轴
图中的Y轴有五个数值,因此我们要在Y轴方向上等分四份,两段线之间的距离我们定义为cellHeight,所以只需要如下绘制即可:
cellHeight = (originY - getPaddingTop()) / 4;
for (int i = 0; i < 5; i++) {
//画横线
canvas.drawLine(originX, originY - cellHeight * i,
width - getPaddingRight(), originY - cellHeight * i, mPaint);
}
等等,左边还有文字没有绘制,横线是与文字中间对齐的,在for循环中添加如下代码:
mPaint.getTextBounds((10+5* i) + "元", 0, ((10+5* i) + "元").length(), textRec);
canvas.drawText((10+5* i) + "元", originX - textRec.width() - 8, originY - cellHeight * i + textRec.height() / 2, mPaint);
OK,至此Y轴画完,只剩下表格内容。
6. 绘制曲线
由于传入的数据中只包含了Y轴坐标,没有X轴的,但我们知道X轴是连续绘制的,所以我们只需要对X轴进行等分:
cellWidth = (width - getPaddingRight() - originX) * 1.0f / (dataSource.size() - 1);
*1.0f是为了消除取整时的误差。
这样每个点的XY坐标就都有了,只需要绘制就可以了:
for (int i = 0; i < dataSource.size() - 1; i++) {
//绘制代码
}
开始时我使用的是canvas.drawLine,很明显绘制出的是折线图,也想用贝塞尔曲线,但一直不知道公式,因为贝塞尔的参数前面是控制点而不是绘制点。参考了下 https://github.com/mychoices/Jgraph的开源,里面的曲线画法公式这样的:
midX=(startX+endX)/2;
path.cubicTo(midX,startY,midX,endY,endX,endY);
OK,至此曲线图表绘制完成,其他扩展根据自己需要而定。
源码下载:https://github.com/FullCat/Chart