自定义View——折线图

来源:互联网 发布:药渡数据 编辑:程序博客网 时间:2024/06/08 15:12

概述

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

0 0
原创粉丝点击