Android用Canvas画一个折线图,并加以简单封装

简介: 本文介绍了如何用Java绘制动态折线图,从固定折线图的实现到封装成可复用的组件。首先通过绘制XY坐标轴、添加坐标标签和绘制折线及数据点完成基础折线图。接着,将静态数据替换为动态输入,支持自定义X轴、Y轴和折线数据。代码中包含关键方法如`drawDaxes`(绘制坐标轴)、`drawAxispoint`(绘制坐标点)和`drawbrokenLine`(绘制折线)。最终实现可根据传入数据动态生成折线图,适用于Android开发中的数据可视化场景。

昨天晚上跟朋友聊天,聊到绘制折线,然后多画了几笔,画了个折线图。(老规矩后面有源码)

先看一下效果图




view宽高设置为正方形
view宽高设置为mach_parent

先来绘制一个固定的折线图

1.先画一个xy坐标轴。

   Paint daxesPaint,axispointPaint,brokenLinePaint;
        //画布宽度
        canvasWidth = canvas.getWidth();
        //画布高度
        canvasHeight = canvas.getHeight();
        widthCriterion = canvasWidth /10;    //将画布宽分为10份
        hightCriterion = canvasHeight /10;   //将画布高分为10份
        minCriterion = widthCriterion > hightCriterion ? hightCriterion /2: widthCriterion /2; //画xy轴角的依据

        daxesPaint=new Paint();
        daxesPaint.setColor(Color.BLACK);
        daxesPaint.setAntiAlias(true);  //去掉锯齿效果
        daxesPaint.setStrokeWidth(7.0f);//画笔宽度
        //第一个方法:画xy轴
        drawDaxes(canvas,daxesPaint);
AI 代码解读
drawDaxes方法如下:
AI 代码解读
 private void  drawDaxes(Canvas canvas,Paint p){
   
       //开始y绘制坐标系
       canvas.drawLine(widthCriterion,hightCriterion,widthCriterion,hightCriterion*9,p);
       //绘制y角
       canvas.drawLine(widthCriterion-minCriterion,hightCriterion+minCriterion,widthCriterion+2,hightCriterion,p);
       canvas.drawLine(widthCriterion,hightCriterion,widthCriterion+minCriterion-2,hightCriterion+minCriterion,p);
       //开始x绘制坐标系
       canvas.drawLine(widthCriterion-4,hightCriterion*9,widthCriterion*9,hightCriterion*9,p);
       //绘制x角
       canvas.drawLine(widthCriterion*9-minCriterion,hightCriterion*9-minCriterion,widthCriterion*9,hightCriterion*9+2,p);
       canvas.drawLine(widthCriterion*9-minCriterion,hightCriterion*9+minCriterion,widthCriterion*9,hightCriterion*9-2,p);

   }
AI 代码解读

效果如下:

4.jpg

2.再绘制x,y轴坐标

 //开始绘制xy轴坐标
        axispointPaint=daxesPaint;
        drawAxispoint(canvas,axispointPaint);
AI 代码解读

drawAxispoint方法如下

 private void drawAxispoint(Canvas canvas,Paint p){
   
       textFont=widthCriterion/5*2;
       Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
       p.setTypeface( font );
       p.setTextSize(textFont);
       for (int i = 1; i <=8 ; i++) {
   
           String text= String.valueOf(-1+i);
           int stringWidth = (int) p.measureText(text);     //文本长度
           canvas.drawText(text, i*widthCriterion-stringWidth/2, hightCriterion*9+textFont, p);// 画文本
       }
       for (int i = 1; i <=7 ; i++) {
   
           String text= String.valueOf(i);
           int stringWidth = (int) p.measureText(text);
           //文本长度
           canvas.drawText(text, widthCriterion-textFont, hightCriterion*9-i*hightCriterion+stringWidth/2, p);// 画文本
       }
   }
AI 代码解读

效果图如下:

5.jpg

3.最后绘制折线和坐标点

 //开始绘制折线和坐标点
        brokenLinePaint=axispointPaint;
        brokenLinePaint.setStrokeWidth(5.0f);
        drawbrokenLine(canvas,brokenLinePaint);
AI 代码解读

drawbrokenLine方法如下:

 private void drawbrokenLine(Canvas canvas,Paint p){
   
       canvas.drawLine(widthCriterion,hightCriterion*9,widthCriterion*2,hightCriterion*2,p);
       canvas.drawLine(widthCriterion*2,hightCriterion*2,widthCriterion*3,hightCriterion*5,p);
       canvas.drawLine(widthCriterion*3,hightCriterion*5,widthCriterion*4,hightCriterion*7,p);
       canvas.drawLine(widthCriterion*4,hightCriterion*7,widthCriterion*5,hightCriterion*6,p);
       canvas.drawLine(widthCriterion*5,hightCriterion*6,widthCriterion*6,hightCriterion*7,p);
       canvas.drawLine(widthCriterion*6,hightCriterion*7,widthCriterion*7,hightCriterion*2,p);
       canvas.drawLine(widthCriterion*7,hightCriterion*2,widthCriterion*8,hightCriterion*3,p);
       //画折线上的点
       canvas.drawCircle(widthCriterion, hightCriterion*9, 10, p);
       canvas.drawCircle(widthCriterion*2,hightCriterion*2, 10, p);
       canvas.drawCircle(widthCriterion*3,hightCriterion*5, 10, p);
       canvas.drawCircle(widthCriterion*4,hightCriterion*7, 10, p);
       canvas.drawCircle(widthCriterion*5,hightCriterion*6, 10, p);
       canvas.drawCircle(widthCriterion*6,hightCriterion*7, 10, p);
       canvas.drawCircle(widthCriterion*7,hightCriterion*2, 10, p);
       canvas.drawCircle(widthCriterion*8,hightCriterion*3, 10, p);

   }
AI 代码解读

效果图如下:

6.jpg

固定的折线图java文件如下:

public class LineChartView extends View {
   

    private int minCriterion;
    private int hightCriterion;
    private int widthCriterion;
    private int canvasHeight;
    private int canvasWidth;
    private int textFont;


    public LineChartView(Context context) {
   
        super(context);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
   
        super(context, attrs);

    }

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

    //绘制
    private void drawAxis(Canvas canvas){
   
        Paint daxesPaint,axispointPaint,brokenLinePaint;
        //画布宽度
        canvasWidth = canvas.getWidth();
        //画布高度
        canvasHeight = canvas.getHeight();
        widthCriterion = canvasWidth /10;    //将画布宽分为10份
        hightCriterion = canvasHeight /10;   //将画布高分为10份
        minCriterion = widthCriterion > hightCriterion ? hightCriterion /2: widthCriterion /2; //画xy轴角的依据

        daxesPaint=new Paint();
        daxesPaint.setColor(Color.BLACK);
        daxesPaint.setAntiAlias(true);  //去掉锯齿效果
        daxesPaint.setStrokeWidth(7.0f);//画笔宽度
        //第一个方法:画xy轴
        drawDaxes(canvas,daxesPaint);

        //开始绘制xy轴坐标
        axispointPaint=daxesPaint;
        drawAxispoint(canvas,axispointPaint);

        //开始绘制折线和坐标点
        brokenLinePaint=axispointPaint;
        brokenLinePaint.setStrokeWidth(5.0f);
        drawbrokenLine(canvas,brokenLinePaint);
    }


   private void  drawDaxes(Canvas canvas,Paint p){
   
       //开始y绘制坐标系
       canvas.drawLine(widthCriterion,hightCriterion,widthCriterion,hightCriterion*9,p);
       //绘制y角
       canvas.drawLine(widthCriterion-minCriterion,hightCriterion+minCriterion,widthCriterion+2,hightCriterion,p);
       canvas.drawLine(widthCriterion,hightCriterion,widthCriterion+minCriterion-2,hightCriterion+minCriterion,p);
       //开始x绘制坐标系
       canvas.drawLine(widthCriterion-4,hightCriterion*9,widthCriterion*9,hightCriterion*9,p);
       //绘制x角
       canvas.drawLine(widthCriterion*9-minCriterion,hightCriterion*9-minCriterion,widthCriterion*9,hightCriterion*9+2,p);
       canvas.drawLine(widthCriterion*9-minCriterion,hightCriterion*9+minCriterion,widthCriterion*9,hightCriterion*9-2,p);

   }

   private void drawAxispoint(Canvas canvas,Paint p){
   
       textFont=widthCriterion/5*2;
       Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
       p.setTypeface( font );
       p.setTextSize(textFont);
       for (int i = 1; i <=8 ; i++) {
   
           String text= String.valueOf(-1+i);
           int stringWidth = (int) p.measureText(text);     //文本长度
           canvas.drawText(text, i*widthCriterion-stringWidth/2, hightCriterion*9+textFont, p);// 画文本
       }
       for (int i = 1; i <=7 ; i++) {
   
           String text= String.valueOf(i);
           int stringWidth = (int) p.measureText(text);
           //文本长度
           canvas.drawText(text, widthCriterion-textFont, hightCriterion*9-i*hightCriterion+stringWidth/2, p);// 画文本
       }
   }

   private void drawbrokenLine(Canvas canvas,Paint p){
   
       canvas.drawLine(widthCriterion,hightCriterion*9,widthCriterion*2,hightCriterion*2,p);
       canvas.drawLine(widthCriterion*2,hightCriterion*2,widthCriterion*3,hightCriterion*5,p);
       canvas.drawLine(widthCriterion*3,hightCriterion*5,widthCriterion*4,hightCriterion*7,p);
       canvas.drawLine(widthCriterion*4,hightCriterion*7,widthCriterion*5,hightCriterion*6,p);
       canvas.drawLine(widthCriterion*5,hightCriterion*6,widthCriterion*6,hightCriterion*7,p);
       canvas.drawLine(widthCriterion*6,hightCriterion*7,widthCriterion*7,hightCriterion*2,p);
       canvas.drawLine(widthCriterion*7,hightCriterion*2,widthCriterion*8,hightCriterion*3,p);
       //画折线上的点
       canvas.drawCircle(widthCriterion, hightCriterion*9, 10, p);
       canvas.drawCircle(widthCriterion*2,hightCriterion*2, 10, p);
       canvas.drawCircle(widthCriterion*3,hightCriterion*5, 10, p);
       canvas.drawCircle(widthCriterion*4,hightCriterion*7, 10, p);
       canvas.drawCircle(widthCriterion*5,hightCriterion*6, 10, p);
       canvas.drawCircle(widthCriterion*6,hightCriterion*7, 10, p);
       canvas.drawCircle(widthCriterion*7,hightCriterion*2, 10, p);
       canvas.drawCircle(widthCriterion*8,hightCriterion*3, 10, p);

   }


}
AI 代码解读

下面来简单封装一下

1.首先提供给外界输入数据的方法:

    public void setChartdate(String[] xdate, int[] ydate, float[] linedate) {
   
        this.xdate = xdate;    //x轴坐标
        this.ydate = ydate;    //y轴坐标
        this.linedate = linedate; //坐标点的y轴上的位置
    }
AI 代码解读

2.进行数据为空判断和越界判断

   if (xdate.length!=0&&ydate.length!=0&&linedate.length!=0&&xdate.length>=linedate.length){
   
            if (yMaxdata()>=lineMaxdata()){
   
                drawAxis(canvas);
            }

        }
AI 代码解读

其中yMaxdata(),lineMaxdata()方法作用为取ydate与linedate中的最大值

3.将所有写死的数据与传进来的数据产生联系。

    private void drawDaxes(Canvas canvas, Paint p) {
   
        //开始y绘制坐标系
        canvas.drawLine(widthCriterion, hightCriterion, widthCriterion, hightCriterion * (yCopies - 1), p);
        //绘制y角
        canvas.drawLine(widthCriterion - minCriterion, hightCriterion + minCriterion, widthCriterion + 2, hightCriterion, p);
        canvas.drawLine(widthCriterion, hightCriterion, widthCriterion + minCriterion - 2, hightCriterion + minCriterion, p);
        //开始x绘制坐标系
        canvas.drawLine(widthCriterion - 4, hightCriterion * (yCopies - 1), widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1), p);
        //绘制x角
        canvas.drawLine(widthCriterion * (xCopies - 1) - minCriterion, hightCriterion * (yCopies - 1) - minCriterion, widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1) + 2, p);
        canvas.drawLine(widthCriterion * (xCopies - 1) - minCriterion, hightCriterion * (yCopies - 1) + minCriterion, widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1) - 2, p);

    }

    private void drawAxispoint(Canvas canvas, Paint p) {
   
        textFont = widthCriterion / 5 * 2;
        Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
        p.setTypeface(font);
        p.setTextSize(textFont);
        //画x轴数据
        for (int i = 0; i < xdate.length; i++) {
   
            String text = xdate[i];
            int stringWidth = (int) p.measureText(text);     //文本长度
            canvas.drawText(text, (i + 1) * widthCriterion - stringWidth / 2, hightCriterion * (yCopies - 1) + textFont, p);// 画文本
        }
        for (int i = 0; i < ydate.length; i++) {
   
            String text = String.valueOf(ydate[i]);

            int stringWidth = (int) p.measureText(text);
            //文本长度
            if (i == 0) {
   
            } else {
   
                canvas.drawText(text, widthCriterion - textFont-stringWidth, hightCriterion * (yCopies - 1) - i * hightCriterion + stringWidth / 2, p);// 画文本

            }
        }
    }

    private void drawbrokenLine(Canvas canvas, Paint p) {
   
        float line=(hightCriterion * (yCopies - 1)-hightCriterion*2)/ydate[ydate.length-1];

        for (int i = 0; i <linedate.length; i++) {
   
            float height=hightCriterion * (yCopies-1)-line*linedate[i];
            if (i!=linedate.length-1){
   
                float elseheight=hightCriterion * (yCopies-1)-line*linedate[i+1];
                canvas.drawLine(widthCriterion*(i+1),height , widthCriterion * (i+2), elseheight, p);
                canvas.drawCircle(widthCriterion*(i+1), height, 10, p);
            }else{
   
                float endheight=hightCriterion * (yCopies-1)-line*linedate[linedate.length-1];
                canvas.drawCircle(widthCriterion*(i+1), endheight, 10, p);
            }

        }
    }
AI 代码解读

现在就可以根据给到的数据动态绘制简单折线图

接下来看效果
在Activity中找到控件后,调用控件的setChartdate()方法;
数据如下:

    private String[] xdata={
   "0","1","2","3","4","5","6","7","8"};
    private int[] yfata={
   0,10,20,30,40,50,60,70};
    private float[] linedata={
   5,10,6,30,5,62.5f,6,2,};
AI 代码解读

传入数据:

    linechartview.setChartdate(xdata,yfata,linedata);
AI 代码解读

效果图如下:
7.png

封装后java代码如下

public class LineChartView extends View {
   

    private int minCriterion;
    private int hightCriterion;
    private int widthCriterion;
    private int canvasHeight;
    private int canvasWidth;
    private int textFont;

    private String[] xdate;
    private int[] ydate;
    private float[] linedate;
    private int xCopies;
    private float yCopies;

    public void setChartdate(String[] xdate, int[] ydate, float[] linedate) {
   
        this.xdate = xdate;
        this.ydate = ydate;
        this.linedate = linedate;
    }


    public LineChartView(Context context) {
   
        super(context);
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
   
        super(context, attrs);

    }

    @Override
    protected void onDraw(Canvas canvas) {
   
        super.onDraw(canvas);
        if (xdate.length!=0&&ydate.length!=0&&linedate.length!=0&&xdate.length>=linedate.length){
   
            if (yMaxdata()>=lineMaxdata()){
   
                drawAxis(canvas);
            }

        }

    }

    //绘制
    private void drawAxis(Canvas canvas) {
   
        xCopies = xdate.length + 2;
        yCopies = ydate.length + 2;
        Paint daxesPaint, axispointPaint, brokenLinePaint;
        //画布宽度
        canvasWidth = canvas.getWidth();
        //画布高度
        canvasHeight = canvas.getHeight();
        widthCriterion = canvasWidth / xCopies;
        hightCriterion = (int) (canvasHeight / yCopies);
        minCriterion = widthCriterion > hightCriterion ? hightCriterion / 2 : widthCriterion / 2;
        //开始绘制底层背景
        daxesPaint = new Paint();
        daxesPaint.setColor(Color.BLACK);
        daxesPaint.setAntiAlias(true);  //去掉锯齿效果
        daxesPaint.setStrokeWidth(7.0f);
        drawDaxes(canvas, daxesPaint);

        //开始绘制坐标点
        axispointPaint = daxesPaint;
        drawAxispoint(canvas, axispointPaint);

        //开始绘制折线和线上的点
        brokenLinePaint=axispointPaint;
        brokenLinePaint.setStrokeWidth(5.0f);
        drawbrokenLine(canvas,brokenLinePaint);
    }


    private void drawDaxes(Canvas canvas, Paint p) {
   
        //开始y绘制坐标系
        canvas.drawLine(widthCriterion, hightCriterion, widthCriterion, hightCriterion * (yCopies - 1), p);
        //绘制y角
        canvas.drawLine(widthCriterion - minCriterion, hightCriterion + minCriterion, widthCriterion + 2, hightCriterion, p);
        canvas.drawLine(widthCriterion, hightCriterion, widthCriterion + minCriterion - 2, hightCriterion + minCriterion, p);
        //开始x绘制坐标系
        canvas.drawLine(widthCriterion - 4, hightCriterion * (yCopies - 1), widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1), p);
        //绘制x角
        canvas.drawLine(widthCriterion * (xCopies - 1) - minCriterion, hightCriterion * (yCopies - 1) - minCriterion, widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1) + 2, p);
        canvas.drawLine(widthCriterion * (xCopies - 1) - minCriterion, hightCriterion * (yCopies - 1) + minCriterion, widthCriterion * (xCopies - 1), hightCriterion * (yCopies - 1) - 2, p);

    }

    private void drawAxispoint(Canvas canvas, Paint p) {
   
        textFont = widthCriterion / 5 * 2;
        Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
        p.setTypeface(font);
        p.setTextSize(textFont);
        //画x轴数据
        for (int i = 0; i < xdate.length; i++) {
   
            String text = xdate[i];
            int stringWidth = (int) p.measureText(text);     //文本长度
            canvas.drawText(text, (i + 1) * widthCriterion - stringWidth / 2, hightCriterion * (yCopies - 1) + textFont, p);// 画文本
        }
        for (int i = 0; i < ydate.length; i++) {
   
            String text = String.valueOf(ydate[i]);

            int stringWidth = (int) p.measureText(text);
            //文本长度
            if (i == 0) {
   
            } else {
   
                canvas.drawText(text, widthCriterion - textFont-stringWidth, hightCriterion * (yCopies - 1) - i * hightCriterion + stringWidth / 2, p);// 画文本

            }
        }
    }

    private void drawbrokenLine(Canvas canvas, Paint p) {
   
        float line=(hightCriterion * (yCopies - 1)-hightCriterion*2)/ydate[ydate.length-1];

        for (int i = 0; i <linedate.length; i++) {
   
            float height=hightCriterion * (yCopies-1)-line*linedate[i];
            if (i!=linedate.length-1){
   
                float elseheight=hightCriterion * (yCopies-1)-line*linedate[i+1];
                canvas.drawLine(widthCriterion*(i+1),height , widthCriterion * (i+2), elseheight, p);
                canvas.drawCircle(widthCriterion*(i+1), height, 10, p);
            }else{
   
                float endheight=hightCriterion * (yCopies-1)-line*linedate[linedate.length-1];
                canvas.drawCircle(widthCriterion*(i+1), endheight, 10, p);
            }

        }
    }
    private float yMaxdata(){
   
        float max = 0;
        for (int i = 0; i < ydate.length; i++) {
   

            if (ydate[i] > max) {
   
                max = ydate[i];

            }
        }
       return max;
    }

    private float lineMaxdata(){
   
        float max = 0;
        for (int i = 0; i < linedate.length; i++) {
   

            if (linedate[i] > max) {
   
                max = linedate[i];

            }
        }
        return max;
    }

}
AI 代码解读

核心:绘制与传入数据产生联系,建议先绘制一次固定的,再自我封装,有利于理解

觉得好的话,点个关注吧!谢谢
如有疑问,欢迎留言。

目录
打赏
0
0
0
0
100
分享
相关文章
Python封装ADB获取Android设备wifi地址的方法
Python封装ADB获取Android设备wifi地址的方法
276 0
Android用Canvas画一个真正能跑的跑马灯
这是一篇关于使用Canvas实现跑马灯效果的技术分享。作者通过一个简单的Demo,展示了如何利用Android的Canvas绘制动态跑马灯。核心思路是通过开启耗时线程让彩色背景旋转,结合坐标计算、渐变色绘制和旋转动画,最终实现动态视觉效果。代码分为三个主要部分:1) 计算View宽高与矩形坐标;2) 绘制背景、跑马灯及幕布;3) 设置旋转逻辑并反向调整幕布。文末附有完整Java代码及XML调用示例,适合初学者学习Canvas绘图原理。
Android 分享机顶盒项目的封装类《GridView》(二)(转)
Android 分享机顶盒项目的封装类《GridView》(二)(转)
83 2
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
Android项目架构设计问题之将隐式跳转的逻辑进行抽象和封装如何解决
100 0
Android自定义View之Canvas一文搞定
这篇文章介绍了Android自定义View中如何使用Canvas和Paint来绘制图形。Canvas可理解为画布,用于绘制各种形状如文字、点、线、矩形、圆角矩形、圆和弧。常见API包括`drawText()`、`drawPoint()`、`drawLine()`、`drawRect()`等。文章还提到了Canvas的保存、恢复、平移和旋转方法,通过绘制钟表盘的例子展示了如何实际应用。总结关键点:Canvas与Paint结合用于图像绘制,掌握Canvas的基本绘图函数及坐标变换操作是自定义View的关键。
271 0
Android自定义View之Canvas一文搞定
Android平台轻量级RTSP服务模块二次封装版调用说明
本文介绍了Android平台上轻量级RTSP服务模块的二次封装实践,旨在简化开发流程,让开发者能更专注于业务逻辑。通过`LibPublisherWrapper`类提供的API,可在应用中轻松初始化RTSP服务、配置视频参数(如分辨率、编码类型)、启动与停止RTSP服务及流发布,并获取RTSP会话数量。此外,还展示了如何处理音频和视频数据的采集与推送。最后,文章提供了从启动服务到销毁资源的完整示例,帮助开发者快速集成实时流媒体功能。
104 0
Android 分享机顶盒项目的封装类《GridView》(三)(转)
Android 分享机顶盒项目的封装类《GridView》(三)(转)
86 2
kotlin安卓开发【Jetpack Compose】:封装SnackBarUtil工具类方便使用
GPT-4o 是一个非常智能的模型,比当前的通义千问最新版本在能力上有显著提升。作者让GPT开发一段代码,功能为在 Kotlin 中使用 Jetpack Compose 框架封装一个 Snackbar 工具类,方便调用

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等