自定义View——折线图

    xiaoxiao2021-03-25  191

    概述

    折线图在很多的项目中都会出现,作为数据直观的展示。在之前的一个房地产数据管理的项目中,用到了很多统计图,起初也在网上看过一些图表库,但是大部分都不能满足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();

    初始化

    绘制折线图,或者是其他的统计图,都可以从以下几点入手:

    1、原点坐标(x,y);

    yCount是y坐标数据的个数 圆点Y轴坐标 上面padding +xTabHeight +(yCount-1个y轴间隔)

    dotY = padding + xTabHeight + (yCount - 1) * yScaleHeight;

    圆点X轴坐标 左边距padding +画笔绘制最大值的长度 + marginXy(距离坐标轴的距离)

    dotX = padding + xYAxisPaint.measureText(maxData + "") + marginXy;

    2、确定View的宽高

    一般宽我们都是设置屏幕宽度,当然也可以从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);

    3、xy轴数据间隔

    y轴数据间隔可读取属性中的值,默认是32dp; x轴数据间隔:x轴长度除以x轴数据个数 xScaleWidth = (chartViewWidth - dotX - padding) / mLineData.getLables().size();

    onMeasure

    宽高测量

    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);

    绘制纵坐标和横线

    /** * 绘制纵坐标以及纵坐标横线 */ private void drawOrdinate(Canvas canvas) { xYAxisPaint.setTextSize(axisSize); xYAxisPaint.setTextAlign(Paint.Align.LEFT); for (int i = 0; i < yCount; i++) { xYAxisPaint.setColor(axisColor); //横线 canvas.drawLine(dotX, dotY - i * yScaleHeight, chartViewWidth - padding, dotY - i * yScaleHeight, xYAxisPaint); //纵坐标文字 xYAxisPaint.setColor(textColor); String xNum = new BigDecimal(maxData) .divide(new BigDecimal(yCount - 1), 0, BigDecimal.ROUND_HALF_UP) .multiply(new BigDecimal(i)).toString(); canvas.drawText(xNum, padding, dotY - i * yScaleHeight, xYAxisPaint); } xYAxisPaint.setColor(axisColor); //竖线 canvas.drawLine(dotX, padding + xTabHeight - 15, dotX, dotY, xYAxisPaint); }

    绘制xtab和折线图

    绘制折线图核心就是要根据数据算出每个点在图上的坐标,

    点的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); } } } }

    onDraw

    @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawOrdinate(canvas); drawAbscissaAndChart(canvas); }

    使用

    layout

    <com.totcy.tchartlibrary.charts.LineChartView android:id="@+id/lineChart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/white" app:ChartLineWidth="1dp" app:ChartPandding="5dp" app:TextSize="10sp" app:YscaleHeight="32dp" />

    Activity

    private void initData() { //just for test //X轴标签 ArrayList<String> lables = new ArrayList<>(); lables.add("一月"); lables.add("二月"); lables.add("三月"); lables.add("四月"); lables.add("五月"); lables.add("六月"); //折线集合 ArrayList<LineDataSet> lineDataSets = new ArrayList<>(); //折线数据1 { LineDataSet lineDataSet1 = new LineDataSet(); lineDataSet1.setDotColor(Color.RED); lineDataSet1.setLineColor(Color.parseColor("#0696f5")); //折线数据1 Y value ArrayList<Entry> entries1 = new ArrayList<>(); entries1.add(new Entry(120, 0)); entries1.add(new Entry(20, 1)); entries1.add(new Entry(80, 2)); entries1.add(new Entry(37, 3)); entries1.add(new Entry(94, 4)); entries1.add(new Entry(234, 5)); lineDataSet1.setyVals(entries1); lineDataSets.add(lineDataSet1); } //折线数据2 { LineDataSet lineDataSet2 = new LineDataSet(); lineDataSet2.setDotColor(Color.RED); lineDataSet2.setLineColor(Color.parseColor("#60b027")); //折线数据2 Y value ArrayList<Entry> entries2 = new ArrayList<>(); entries2.add(new Entry(50, 0)); entries2.add(new Entry(70, 1)); entries2.add(new Entry(150, 2)); entries2.add(new Entry(77, 3)); entries2.add(new Entry(407, 4)); entries2.add(new Entry(124, 5)); lineDataSet2.setyVals(entries2); lineDataSets.add(lineDataSet2); } mLineData = new LineData(lables, lineDataSets); } public void onClick(View view) { //mLineChartView.setLineData(mLineData); mLineChartView.setLineDataWithAnim(mLineData); }

    这是一个普通的自定义折线图,当然还有很多需要改进的地方,以后也会写一些条形图、以及组合使用的控件。 代码戳github。

    转载请注明原文地址: https://ju.6miu.com/read-2266.html

    最新回复(0)