折线图实现

来源:互联网 发布:快速排序算法实现 编辑:程序博客网 时间:2024/06/05 21:04

      这样的图用来做统计最方便了,今天,我们又要摆脱第三方的约束,自己来实现了,是不是很开心,现在就来动手吧。

      本文内容需要读者具备一定的自定义view基础,否则看起来可能比较费力,不过懂的看门道,不懂的可以凑个热闹,能看懂可以自己去改不好的地方,不懂的也可以直接拿来用。
先给个效果图:
这里写图片描述这里写图片描述

      看起来还可以吧,废话不多说,开始我们的绘制。

     首先要确定我们要达成的效果,下面列出来

1:数据的获取2:折线的绘制3:X坐标文字的绘制4:数字的绘制5:数字的位置判断,防止与折线重合6:随手指滑动的效果

这样思路清晰了,下面逐个击破

 1    数据的获取
    这恐怕是最简单的一步了,我在里面定义了一个方法,包含三个参数,使用的时候直接传进去,再刷新试图即可,给出方法

    //设置整个坐标上的一些文字和数字    public void setData(String[] coordinate, String[] number, float extremum) {        this.coordinate = coordinate;        this.number = number;        this.extremum = extremum;    }

    可以看见传入了三个参数,一个是坐标数组,一个是数字数组,这里用字符数组代替的,再一个就是Y轴上最大值

 2    折线的绘制
    绘制折线肯定是要使用到path,那么就要把path拿来,然后添上路径即可,那么主要就是路径坐标的问题了,X坐标我用的文字宽度加上文字和文字之间距离形成最后的坐标,然后遍历数组就能获得一个个X坐标,Y坐标我添加了一个功能,那就是能设置绘制图的上边距和下边距,其实X坐标我也设置了左边距,具体的使用大家可以去里面更改数值感受一下,如果上边距加上下编剧正好等于控件的高度的话,那就整个图就是一个直线,你懂的。
给出绘制路径代码:

            float deviationY = (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM;   //这里式子没有简化,是为了方便你们清楚这是怎么一个过程            float deviationX = movedistance + OFFSETX + txtrect.width() * i + txtpading * i;  //这里式子没有简化,是为了方便你们清楚这是怎么一个过程            //画出折线图的路径            path.lineTo(deviationX, deviationY);            canvas.drawPath(path, linepaint);      //绘制折线

    解释一下,这里的movedistance就是随手指滑动会产生的位移,OFFSETX则是X轴左边距,OFFSETYTOP是Y轴上边距,OFFSETYBOTTOM是Y轴下边距。

 3    X坐标文字的绘制
    文字的绘制比较简单了,主要就是坐标的把握,Y轴没难度,都用问题的高度即可,X轴呢,其实就是刚才绘制折线的X坐标再减去文字宽度的一半即可,给出代码参考:

canvas.drawText(coordinate[i] + "", deviationX - txtrect.width() / 2, height - txtrect.height() / 2, extremumpaint);     //绘制X轴上的坐标

 4    数字的绘制
    数字的绘制相对于文字的绘制就是多了一个Y轴上的变换,当然了,X轴这里要减去的是数字的宽度一半,而不是文字了,高度我们其实用之前折线的高度减去上我们数字的高度的一半即可,给出代码:

canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint);   //绘制数字

 5    数字的位置判断,防止与折线重合
    这个问题呢我们可以从折线的走势判断解决,那么当前的数字所在的折线坐标会有以下情况:
    1: 中间最高
    2: 左边最高,右边最低
    3: 左边最低,右边最高
    4: 中间最低
就这四种情况,我们可以从Y轴进行判断,判断我提取了成了一个方法,给出代码:

    /*这个函数的作用是判断当前数字坐标的y轴和其左右两边的数字坐标的y进行比较,从而判断数字显示的位置,避免折线和数字重复在一起的情况     */    public int judgmentposition(int i) {        int status = 1;        if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) >                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM <                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 1;        } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) <                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM <                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 2;        } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) >                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM >                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 3;        } else {            status = 4;        }        Log.d("statusnumbers", status + "");        return status;    }

使用时的代码:

            //下面的绘制要保证文字在合适的位置,想了解的可参考我另一个博客:http://blog.csdn.net/wanxuedong/article/details/69396732            if (i > 0 && i < number.length - 1) {                switch (judgmentposition(i)) {                    case 1:    //中间最高                        canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height(), txtpaint);   //绘制数字                        break;                    case 2:    //左边最高,右边最低                        canvas.drawText(number[i] + "", deviationX, deviationY - numberrect.height() / 2, txtpaint);   //绘制数字                        break;                    case 3:    //左边最低,右边最高                        canvas.drawText(number[i] + "", deviationX - numberrect.width(), deviationY - numberrect.height() / 2, txtpaint);   //绘制数字                        break;                    case 4:    //中间最低                        canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY + numberrect.height() * 2, txtpaint);   //绘制数字                        break;                }            } else {                canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint);   //绘制数字            }

 6    随手指滑动的效果
    和手势有关的就不得不使用onTouchEvent方法了,给出代码:

    //这里面我们实现了让该控件可以随手指挥动而滚动,但是没写滚动条(因为觉得有滚动条也不好看)    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                positionx = event.getRawX();                break;            case MotionEvent.ACTION_MOVE:                //如果是往右滑动,那么左侧距离就要判断不能过大                if (event.getRawX() - positionx > 0) {                    if (!(movedistance > leftdistance)) {                        movedistance += event.getRawX() - positionx;                        positionx = event.getRawX();                        invalidate();                    }                } else {    //如果是往右滑动,那么左侧距离也要判断不能过大                    if (!(movedistance < -rightdistance)) {                        movedistance += event.getRawX() - positionx;                        positionx = event.getRawX();                        invalidate();                    }                }                positionx = event.getRawX();                break;            case MotionEvent.ACTION_UP:                break;        }        return true;    }

    有两个值需要解释一下,leftdistance和rightdistance,这是我们可以再开始时设置的左右滑动的最大距离,具体可以自己进去设置。

    好了,总的效果也就这样完成了,解剖后是不是看起来又有点简单了,学习就是这样,能者不惧,你就是能者。
下面给出整个代码(可以直接粘贴复制进行使用):

import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;/** * Created by wanxuedong on 2017/08/30. * 联系QQ:2381144912 */public class LineChart extends View {    //这三个值是我们传进来的,绘图的数据从这里来    private String[] coordinate; //X坐标上的标记    private String[] number;      //折线图上的一个个数字,传值的时候只能传数字,别瞎传哈    private float extremum;     //Y坐标的最大值//    private int width;           //控件的宽度    private int height;         //控件的高度    private Path path;          //折线的路径    private Paint linepaint;    //折线的画笔    private Paint rectpaint;    //方块的画笔    private Paint extremumpaint;  //坐标的画笔    private Paint txtpaint;      //数值文字的画笔    //    private Rect rect;          //方块的模型    private Rect txtrect;     //利用rect获取X坐标上文字的宽度,好精确的放置文字位置    private Rect numberrect;   //利用rect获取数字的宽度,好精确的放置文字位置    //下面两个属性用来操作手势有关的    private float positionx = 0;     //手指移动的时候,记录X坐标的作用    private float movedistance;     //手指每次移动的位移,整个图像会根据手势进行移动    /*    * 上面的数据基本上不需要动,下面的属性我们可以自行更改    * */    //    private int RECTWIDTH = 5;       //小方块的宽度    private int RECTHEIGHT = 6;     //小方块的高度和圆的半径    private int OFFSETYTOP = 100;    //Y轴方向顶端留下的长度    private int OFFSETYBOTTOM = 100;    //Y轴方向底端留下的长度    private int OFFSETX = 100;    //x轴方向左端留下的长度    private int XTXTSIZE = 24;   //X轴文字大小    private int NUMBERSIZE = 18; //数字文字大小    private int LINEWIDTH = 3;     //折线粗度    private int txtpading = 80;    //X坐标轴上每个文字之间的间隔大小,这个决定了你的控件有多宽    //下面两个属性需要根据实际情况自己改动,如果有好的方案,望请告知    private int leftdistance = 50;     //X轴手势往右滑动的最大距离,这个数字会和OFFSETX加起来,总的数值才是左侧看起来的距离    private int rightdistance = 500; //X轴手势往左滑动的最大距离    public LineChart(Context context) {        super(context);        path = new Path();        initpaint();    }    public LineChart(Context context, AttributeSet attrs) {        super(context, attrs);        path = new Path();        initpaint();    }    //测量获取控件宽高    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);//        width = getMeasuredWidth();        height = getMeasuredHeight();    }    //这里面开始绘制文字,数字,和折线    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //定位折线的起始位置,也就是第一个数字坐标点        path.moveTo(OFFSETX + movedistance, (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[0]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM);        for (int i = 0; i < number.length; i++) {//            rect = new Rect();            txtrect = new Rect();            numberrect = new Rect();            extremumpaint.getTextBounds(coordinate[i], 0, coordinate[i].length(), txtrect);            txtpaint.getTextBounds(number[i], 0, number[i].length(), numberrect);            float deviationY = (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM;   //这里式子没有简化,是为了方便你们清楚这是怎么一个过程            float deviationX = movedistance + OFFSETX + txtrect.width() * i + txtpading * i;  //这里式子没有简化,是为了方便你们清楚这是怎么一个过程            //画出折线图的路径            path.lineTo(deviationX, deviationY);            canvas.drawPath(path, linepaint);      //绘制折线            //设置方块的位置和宽高属性//            rect.set(width / number.length * i - RECTWIDTH / 2 + OFFSETX, (int) (deviationY - RECTHEIGHT / 2), width / number.length * i + RECTWIDTH / 2 + OFFSETX, (int) (deviationY + RECTHEIGHT / 2));//            canvas.drawRect(rect, rectpaint);      //绘制小方块//            下面是绘制小圆球,上面是绘制小方块,有需要的可以恢复上面的小方块(不过坐标自己去设置了,懒的再去看去改了)            canvas.drawCircle(deviationX, deviationY, RECTHEIGHT, rectpaint);            //下面的绘制要保证文字在合适的位置,想了解的可参考我另一个博客:http://blog.csdn.net/wanxuedong/article/details/69396732            if (i > 0 && i < number.length - 1) {                switch (judgmentposition(i)) {                    case 1:    //中间最高                        canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height(), txtpaint);   //绘制数字                        break;                    case 2:    //左边最高,右边最低                        canvas.drawText(number[i] + "", deviationX, deviationY - numberrect.height() / 2, txtpaint);   //绘制数字                        break;                    case 3:    //左边最低,右边最高                        canvas.drawText(number[i] + "", deviationX - numberrect.width(), deviationY - numberrect.height() / 2, txtpaint);   //绘制数字                        break;                    case 4:    //中间最低                        canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY + numberrect.height() * 2, txtpaint);   //绘制数字                        break;                }            } else {                canvas.drawText(number[i] + "", deviationX - numberrect.width() / 2, deviationY - numberrect.height() / 2, txtpaint);   //绘制数字            }            //上面为了数字位置进行了判断设置,但是不包括第一个数字和最后一个数字,有需要的可以自行添加            canvas.drawText(coordinate[i] + "", deviationX - txtrect.width() / 2, height - txtrect.height() / 2, extremumpaint);     //绘制X轴上的坐标        }        path.reset();   //每次重绘的时候需要把path恢复成空的路径,否则,所有路径会叠加在一起,那到时就热闹了    }    //这里面我们实现了让该控件可以随手指挥动而滚动,但是没写滚动条(因为觉得有滚动条也不好看)    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                positionx = event.getRawX();                break;            case MotionEvent.ACTION_MOVE:                //如果是往右滑动,那么左侧距离就要判断不能过大                if (event.getRawX() - positionx > 0) {                    if (!(movedistance > leftdistance)) {                        movedistance += event.getRawX() - positionx;                        positionx = event.getRawX();                        invalidate();                    }                } else {    //如果是往右滑动,那么左侧距离也要判断不能过大                    if (!(movedistance < -rightdistance)) {                        movedistance += event.getRawX() - positionx;                        positionx = event.getRawX();                        invalidate();                    }                }                positionx = event.getRawX();                break;            case MotionEvent.ACTION_UP:                break;        }        return true;    }    //初始化画笔,读者看的时候可略过    public void initpaint() {        linepaint = new Paint();        rectpaint = new Paint();        extremumpaint = new Paint();        txtpaint = new Paint();        //折线的一些属性        linepaint.setColor(Color.argb(255, 245, 133, 61));     //设置颜色        linepaint.setStyle(Paint.Style.STROKE);    //设置空心填充        linepaint.setAntiAlias(true);  //设置抗锯齿        linepaint.setStrokeWidth(LINEWIDTH);    //设置粗度        //方块的一些属性        rectpaint.setColor(Color.argb(255, 245, 133, 61));        //X坐标轴上文字的一些属性        extremumpaint.setColor(Color.argb(255, 51, 51, 51));        extremumpaint.setTextSize(XTXTSIZE);    //设置坐标文字大小        extremumpaint.setAntiAlias(true);        //数字的一些属性        txtpaint.setColor(Color.argb(255, 145, 145, 145));        txtpaint.setTextSize(NUMBERSIZE);    //设置数字大小        txtpaint.setAntiAlias(true);    }    /*这个函数的作用是判断当前数字坐标的y轴和其左右两边的数字坐标的y进行比较,从而判断数字显示的位置,避免折线和数字重复在一起的情况     */    public int judgmentposition(int i) {        int status = 1;        if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) >                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM <                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 1;        } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) <                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM <                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 2;        } else if (((height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i - 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) >                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM &&                (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM >                        (height - OFFSETYTOP) - (height - OFFSETYTOP - OFFSETYBOTTOM) * (Float.parseFloat(number[i + 1]) / extremum) + OFFSETYTOP - OFFSETYBOTTOM) {            status = 3;        } else {            status = 4;        }        Log.d("statusnumbers", status + "");        return status;    }    //设置整个坐标上的一些文字和数字    public void setData(String[] coordinate, String[] number, float extremum) {        this.coordinate = coordinate;        this.number = number;        this.extremum = extremum;    }}

最后还是给出demo,走你:http://download.csdn.net/download/wanxuedong/9958951

原创粉丝点击