项目需要走势图,什么柱状图、走势图,网上都有一堆现成的框架,但是我项目中只需要走势图,我用第三方框架简直是累赘,毕竟占大小,而且走势图也简单,所以就自己写了,好久没写博客了,就记下吧,供大家参考下,其实很简单,大佬勿喷。先不废话,看效果图吧:
记录了七天的走势,原点的x坐标不是0,因为我们不是用来做坐标轴,而是记录数据,原点的坐标根据实际需求情况而定的。
好了,废话说完了,开始动手吧,动手之前我们得先想想怎么实现,思路是怎样的。
首先,我们需要知道我们需要画什么。
1.我们需要画x,y轴;
2.我们需要画x,y轴的刻度和刻度值;
3.我们需要画y轴上的每个刻度的横线;
4.我们需要画数据的点;
5.数据的点需要连线;
大致就是这些,还有些文字的大小啊,颜色啊等等,都可以自己设置;
然后,我们需要知道怎么画。
大家都知道在视图坐标系中,一个view,原点是它的左上角,所以我们需要实际的原点,真实的原点,即走势图中x,y轴的交接点。所以我们得先确定实际原点,假设实际原点的坐标是(xo,yo),averageX代表x轴每个刻度间的距离,那图片中x轴的11.2所在的坐标就是相对于原点的(averageX,0),所以实际坐标就是(xo+averageX,yo+0)。那么y轴的1000所在的坐标就是相对于原点的(0,-averageY),所以实际坐标是(0+xo,-averageY+yo)注意,android中,x轴向右为正,y轴向下为正,所以1000所在的坐标是负的。哎,语文没学好,可能表达有点不清楚?总之呢,就是为了确定每个点的算法才这样计算的。
首先,我们自定义View继承自View,然后相关属性:
private Context context;
//x,y轴的画笔,刻度文字的画笔,数据点的画笔,数据连线的画笔,y轴刻度横线的画笔,数据点文字的画笔
private Paint xyPaint, textPaint, pointPaint, pointslinePaint, yLinePaint, pointTextPaint;
private int padding; //内部距离,设置个默认的padding值
private int width, height; //控件实际高度和宽度
private float xSize, ySize;//x和y轴的长度(因为控件留了内部距离,不然x,y轴就贴着控件边缘导致看不清,有内部距离,所以长度和控件的宽高不一样了)
private int defaultWidth; //默认宽度
private int defaultHeight; //默认高度
private int xOrigin, yOrigin; //相对原点的坐标
private String[] xPoints; //x轴显示的坐标
private String[] yPoints; //y轴显示的坐标
private List<Float> xPointList, yPointList; //数据点的x坐标集合,y坐标集合;
然后初始化画笔,默认高宽等等:
private void init() {
xPointList = new ArrayList<>();
yPointList = new ArrayList<>();
defaultWidth = (DisplayUtils.width > 480 ? 480 : DisplayUtils.width);
defaultHeight = 320;
padding = DisplayUtils.dip2px(context, 35);
xyPaint = new Paint();
xyPaint.setStyle(Paint.Style.FILL);
xyPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setStyle(Paint.Style.FILL);
textPaint.setAntiAlias(true);
textPaint.setTextSize(DisplayUtils.dip2px(context, 10));
pointPaint = new Paint();
pointPaint.setStyle(Paint.Style.FILL);
pointPaint.setAntiAlias(true);
pointslinePaint = new Paint();
pointslinePaint.setStyle(Paint.Style.FILL);
pointslinePaint.setAntiAlias(true);
pointslinePaint.setStrokeWidth(6);
yLinePaint = new Paint();
yLinePaint.setStyle(Paint.Style.FILL);
yLinePaint.setAntiAlias(true);
pointTextPaint = new Paint();
pointTextPaint.setStyle(Paint.Style.FILL);
pointTextPaint.setAntiAlias(true);
pointTextPaint.setTextSize(DisplayUtils.dip2px(context, 6));
}
为了简单,我就不再attr中设置属性了,直接在代码里面设置颜色值之类的,大家知道思路就好。
设置一个public方法供外部调用来设置数据点的坐标,我们用Map来保存坐标:
public void setData(String[] xPoints, String[] yPoints, Map<Float, Float> showPointsMap) {
this.xPoints = xPoints;
this.yPoints = yPoints;
for (float x : showPointsMap.keySet()) {
xPointList.add(x);
yPointList.add(showPointsMap.get(x));
}
}
设置一个public方法供外部调用,来设置画笔的颜色:
//x,y轴和刻度文字的画笔,数据点的画笔,数据连线的画笔,y轴刻度横线的画笔,数据点文字的画笔
public void setColor(int xyColor, int pointColor, int lineColor, int yLineColor, int pointTextColor) {
xyPaint.setColor(xyColor);
textPaint.setColor(xyColor);
pointPaint.setColor(pointColor);
pointslinePaint.setColor(lineColor);
yLinePaint.setColor(yLineColor);
pointTextPaint.setColor(pointTextColor);
}
然后接下来开始自定义的方法,先onMeasure()方法确定大小,如果测量模式不是EXACYLY,那么就使用默认的高宽值:
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = defaultWidth;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = defaultHeight;
}
ySize = height - padding - 60; //y轴总长度,y轴的paddingTop我只留60px,paddingButtom留padding大小,可以自己定义
xSize = width - padding * 2; //x轴总长度,x方向的左右padding都设置为padding大小
xOrigin = padding; //实际原点的x坐标
yOrigin = height - padding; //实际原点的y坐标
setMeasuredDimension(width, height);
然后重要的onDraw()方法:
float averageHeight = ySize / 5; //y轴每个刻度之间的长度,设置6个刻度
float averageWidth = xSize / 6; //x轴每个刻度之间的长度,设置7个刻度
float averagePx = ySize / 5000;
canvas.drawLine(xOrigin, yOrigin, xOrigin + xSize + 15, yOrigin, xyPaint); //画x轴,多画了15px让x轴右边可以出来,不然顶部正好是最后一个刻度线
canvas.drawLine(xOrigin, yOrigin, xOrigin, yOrigin - ySize - 15, xyPaint); //画y轴,多画了15px让y轴上边可以出来,不然顶部正好是最后一个刻度线
for (int i = 0; i < yPoints.length; i++) { //画y轴上的刻度,y轴刻度的横线,y轴刻度文字
canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin - 15, yOrigin - averageHeight * (i + 1), xyPaint);
canvas.drawLine(xOrigin, yOrigin - averageHeight * (i + 1), xOrigin + xSize + 15, yOrigin - averageHeight * (i + 1), yLinePaint);
canvas.drawText(yPoints[i], 10, yOrigin - averageHeight * (i + 1), textPaint);
}
for (int i = 0; i < xPoints.length; i++) { //画x轴上的刻度和文字
canvas.drawLine(xOrigin + averageWidth * i, yOrigin, xOrigin + averageWidth * i, yOrigin + 15, xyPaint);
canvas.drawText(xPoints[i], xOrigin + averageWidth * (i), height - 30, textPaint);
}
for (int i = 0; i < xPointList.size(); i++) { //画走势连线
if (i < xPointList.size() - 1) {
canvas.drawLine(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i),
xOrigin + averageWidth * (i + 1), yOrigin - averagePx * yPointList.get(i + 1), pointslinePaint);
}
}
for (int i = 0; i < xPointList.size(); i++) { //画数据点,先画线是为了让点可以盖住线
canvas.drawCircle(xOrigin + averageWidth * (i), yOrigin - averagePx * yPointList.get(i), 10, pointPaint); //数据点
canvas.drawText(yPointList.get(i) + "", xOrigin + averageWidth * (i) + 20, yOrigin - averagePx * yPointList.get(i), pointTextPaint); //数据文字
}
思路其实很简单,也没任何难点,就是画点,画线,画刻度。需要说的都在代码中写明了,就不啰嗦了,主要是知道思路,剩下的就简单了。
其实还有很多可以优化的地方,比如自定义属性颜色值,字体大小,线条粗细,数据点的大小,刻度值的位置等等,都可以自定义,这些比较简单我就不啰嗦了,有兴趣的朋友可以去试试。对于柱状图,其实一样的原理。