自定义View——折线图

Posted 小向往

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View——折线图相关的知识,希望对你有一定的参考价值。

概述

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

以上是关于自定义View——折线图的主要内容,如果未能解决你的问题,请参考以下文章

Excel中制作折线图的横坐标是按时间顺序自动排列的,请问如何设置在横坐标上显示自定义的时间?

自定义view—折线图

自定义View——折线图

自定义View——折线图

自定义view——芝麻分折线图

andoid 自定义view 画折线图