折线图在很多的项目中都会出现,作为数据直观的展示。在之前的一个房地产数据管理的项目中,用到了很多统计图,起初也在网上看过一些图表库,但是大部分都不能满足UI给出的样式,所有只能自己搞了,把项目中的代码抽离了一些出来,下面手写了一个折线图。
根据自己的需要,定义一些属性,其他使用默认的也行。
<declare-styleable name="TotcyChart"> <attr name="TextSize" format="dimension"/> <attr name="YscaleHeight" format="dimension" /> <attr name="ChartPandding" format="dimension" /> <attr name="ChartLineWidth" format="dimension" /> </declare-styleable>构造方法中获取。
/** * 获得所有自定义的参数的值 */ TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TotcyChart, defStyleAttr, 0); int n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); if (attr == R.styleable.TotcyChart_TextSize) { axisSize = (int) a.getDimension(attr, axisSize); } else if (attr == R.styleable.TotcyChart_YscaleHeight) { yScaleHeight = (int) a.getDimension(attr, yScaleHeight); } else if (attr == R.styleable.TotcyChart_ChartPandding) { padding = a.getDimension(attr, padding); } else if (attr == R.styleable.TotcyChart_ChartLineWidth) { lineStrokeWidth = a.getDimension(attr, lineStrokeWidth); } else { } } a.recycle();绘制折线图,或者是其他的统计图,都可以从以下几点入手:
yCount是y坐标数据的个数 圆点Y轴坐标 上面padding +xTabHeight +(yCount-1个y轴间隔)
dotY = padding + xTabHeight + (yCount - 1) * yScaleHeight;圆点X轴坐标 左边距padding +画笔绘制最大值的长度 + marginXy(距离坐标轴的距离)
dotX = padding + xYAxisPaint.measureText(maxData + "") + marginXy;一般宽我们都是设置屏幕宽度,当然也可以从xml中读取固定的值;
//宽度测量 if (widthMode == View.MeasureSpec.EXACTLY) { width = widthSize; } else { width = Utils.getScreenWidth(getContext()); }View的高:从上往下计算
//高度测量 上边距padding + (yCount-1个y轴间隔) + marginXy +xTabHeight + padding chartViewHigth = (int) (dotY + 2 * marginXy + xTabHeight + padding);y轴数据间隔可读取属性中的值,默认是32dp; x轴数据间隔:x轴长度除以x轴数据个数 xScaleWidth = (chartViewWidth - dotX - padding) / mLineData.getLables().size();
宽高测量
int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); int widthMode = View.MeasureSpec.getMode(widthMeasureSpec); int heightMode = View.MeasureSpec.getMode(heightMeasureSpec); int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); int height; int width; //宽度测量 if (widthMode == View.MeasureSpec.EXACTLY) { width = widthSize; } else { width = Utils.getScreenWidth(getContext()); } chartViewWidth = width; //圆点X轴坐标 最大值的长度 dotX = padding + xYAxisPaint.measureText(maxData + "") + marginXy; //高度测量 上面padding + (yCount-1个y轴间隔) + marginXy +xTabHeight + padding chartViewHigth = (int) (dotY + 2 * marginXy + xTabHeight + padding); //计算出折线点之间的间隔 if (mLineData != null) xScaleWidth = (chartViewWidth - dotX - padding) / mLineData.getLables().size(); if (heightMode == View.MeasureSpec.EXACTLY) { height = heightSize; } else { height = chartViewHigth; } setMeasuredDimension(width, height);绘制折线图核心就是要根据数据算出每个点在图上的坐标,
点的x坐标是从原点dotX开始,依次增加xScaleWidth; 点的y坐标:这里就需要一个换算: Y轴数据的最大值所占的高度 = (yCount - 1) * yScaleHeight) 因为画布中的圆点是在左上角,所以点的高度=(1-数据/最大值 ) * 最大高度 + 上边距最后用canvas.drawPath()绘制路径折线;
/** * 获得折线图点的高度 * * @return */ private float getBarHeight(float amt) { float result = (new BigDecimal(1).subtract(new BigDecimal(amt).divide(new BigDecimal(maxData), 2, BigDecimal.ROUND_HALF_UP))) .multiply(new BigDecimal((yCount - 1) * yScaleHeight)).add(new BigDecimal(padding + xTabHeight)).floatValue(); return result < dotY ? result : dotY; } /** * 绘制xtab和折线图 */ private void drawAbscissaAndChart(Canvas canvas) { if (mLineData == null) return; xYAxisPaint.setColor(textColor); xYAxisPaint.setTextSize(axisSize); for (int i = 0; i < mLineData.getLables().size(); i++) { String xLable = mLineData.getLables().get(i); xYAxisPaint.setTextAlign(Paint.Align.LEFT); //横坐标文字 canvas.save(); canvas.drawText(xLable, dotX + i * xScaleWidth, dotY + marginXy + xTabHeight, xYAxisPaint); canvas.restore(); } //多条折线 for (LineDataSet dataSet : mLineData.getDataSet()) { Path path = new Path(); path.moveTo(dotX, getBarHeight(dataSet.getyVals().get(0).getVal())); int size = dataSet.getyVals().size(); //绘制折线 for (int i = 1; i < size; i++) { Entry entry = dataSet.getyVals().get(i); //折线 path路径的点 path.lineTo(dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal())); } //平滑曲线设置 if (isSmooth) { CornerPathEffect cornerPathEffect = new CornerPathEffect(10); chartPaint.setPathEffect(cornerPathEffect); } else { CornerPathEffect cornerPathEffect = new CornerPathEffect(0); chartPaint.setPathEffect(cornerPathEffect); } //空心 chartPaint.setStyle(Paint.Style.STROKE); chartPaint.setColor(dataSet.getLineColor()); canvas.drawPath(path, chartPaint); //绘制折线的圆点和每个点的的值(不在上一个循环内写是因为这些点和值要在折线的上面显示) if (isShowValue || (!isSmooth && isIntersection)) for (int i = 0; i < size; i++) { Entry entry = dataSet.getyVals().get(i); //绘制折线的圆点 不能是圆滑的曲线 if (!isSmooth && isIntersection) { chartPaint.setStyle(Paint.Style.FILL); chartPaint.setColor(dataSet.getDotColor()); canvas.drawCircle(dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal()), lineStrokeWidth * 2, chartPaint); } if (isShowValue) { //绘制折线点上的值 xYAxisPaint.setTextSize(axisSize * 2 / 3); xYAxisPaint.setTextAlign(Paint.Align.LEFT); canvas.drawText((int) entry.getVal() + "", dotX + i * xScaleWidth * percent, getBarHeight(entry.getVal()) - marginXy, xYAxisPaint); } } } }这是一个普通的自定义折线图,当然还有很多需要改进的地方,以后也会写一些条形图、以及组合使用的控件。 代码戳github。