Android custom control: implementation of graphic report (line chart, curve chart, dynamic curve chart) (view and surfaceview implement chart control respectively)

Time:2020-1-22

Graphic report is very common, because the display data is relatively intuitive, and there are many common forms, such as: line chart, column chart, pie chart, radar chart, stock chart, and some 3D effect charts.
Android also has many third-party chart libraries, but it is difficult to meet all kinds of needs.
If the third-party library can not meet our needs, we need to write such a control ourselves.

Often after the app requirements are given, many developers have no way to start and do not know how to write.
Today, I just took some time to make a small demo and explain it to you.
In this section, we mainly share the basic process of custom chart, and will not involve too complex knowledge points.
We still according to: demand, analysis, design, implementation, summary this way to explain to you!!!
It’s easier for us to understand.
***

demand

First on the renderings:
页面1:曲线图.gif

页面2:动态曲线图.gif

Requirements:
1. data:
–Simulate the haze value of 50 days. The daily value is a random number within 100 days;
–Take the current date as the last day, and take the data of 50 days forward, that is, 50 items;
2. Business logic
–When the page is loaded, the data is requested and displayed on the chart;
–Click [refresh] data, re request the data and display it on the chart;
3.View
–The background color of the chart is dark gray;
–The color of chart background border is light blue: ා 999dd2;
–The curve color is blue: ා7176ff;
–The text color is white;
–The value of padding can be set in the chart;
–Chart full display data, i.e. adaptive display;
–The numerical text on the curve is displayed in the corresponding position;
–The start and end dates are displayed on the left and right sides of the X axis, and are aligned with the left and right border lines;
–The chart shall support two viewing modes: overall load (full load) and item by item load (dynamic load)


Analysis

1. The data is relatively simple, just make a random number, omitted;
2. Business logic, simple and simple;
3. View, the focus of this section, needs to be analyzed in detail:
3.1 how to implement this chart control?

General practice: use canvas and brush to draw. 
How to draw: drawing on canvas with a brush
(the canvas class provides many drawing methods, and the brush can set various stroke effects.).


Suggestion: you'd better understand the usage of canvas and brush in advance.

3.2 how to draw the background color?

Canvas.drawcolor (parameter: color) is OK, very simple, that is: the canvas directly fills the background color without the brush.

3.3 how to realize the background border line?

Scheme 1: define the path first, record the information of each border line, and then use canvas.drawpath to draw;
Scheme 2: use canvas.drawline to draw each horizontal line and vertical line respectively;


Suggestion: when there are multiple lines, canvas.drawpath management is simpler and drawing is more convenient.

3.4 how to draw the curve?

We can think of it as a two-dimensional coordinate system, including x-axis and y-axis;
So, how can the curve data be displayed properly in the coordinate system?
In fact, it is not difficult. We can determine the size of the canvas (or the size of the control (if the size of the canvas is equal to the size of the control),
Calculate the position information of each data in x-axis and Y-axis of the curve, and then connect these position points into a line;
The X axis should show the location of the data:
The chart can adapt to the full amount of data as a reference (that is, all data can be displayed, in this demo, there are 50 points of haze data):
The length of x-axis should correspond to the total number of data, so the position of each data in x-axis should be:
    Interval of each data in x-axis = X-axis length / number of data pieces;
    Position of each data in x-axis = n-th data * interval;
The Y axis should show the location of the data:
The chart can adapt to the full amount of data as a reference,
The area of y-axis should be able to contain all data sizes. Then, we need to first obtain the maximum and minimum values of data corresponding to it,
The position of each data num in Y axis shall be:
    Y-axis ratio of each data = (Num min) / (max min);
    Position of each data in Y-axis = ratio * Y-axis length;
If we get the position of the data in the X and Y axes, we can draw the curve,
Path is still used here to collect the location of each data point, and the curve is used to connect,
Path.quadto (x1, Y1, X2, Y2) (this method will be introduced later);
Then draw the curve path on the canvas: canvas.drawpath (path, paint);

3.5 how to draw text?

Use canvas.drawtext (text, x, y, paint);
However, the calculation of X and Y positions is a little more complicated. You can take a look at the introduction of this article:
https://www.jianshu.com/p/3e48dd0547a0
Article drawing foundation drawing text

文本绘制原理
Text drawing differences:

Text is not drawn from the top left corner of the text, but based on the baseline.
Give an example:
If we want to draw text in the upper left corner of the custom control,
You might write canvas.drawtext ("mfgia", 0, 0, paint);
But when we write this, we find that the top left corner of the control will only display the content below the baseline,
You can only see the lower half of the letter G,
Other parts, because they are beyond the upper boundary of the custom control, are not drawn.

It doesn’t matter if we don’t understand. Let’s learn the main knowledge first.
If you want to control the position of the text precisely, please refer to this article.

3.6 how to draw dynamic chart?
In fact, the dynamic effect of the chart is to redraw it every certain time, that is, the dynamic effect (video effect is the same principle);
The reason for the two effects (non dynamic / dynamic) is to let you know the usage difference between view and surfaceview.
The main differences are as follows:

View    
--Refresh only in the main thread.
   Disadvantages: if the drawing content is too much or the frequency is too high, the FPS of the main thread will be affected, resulting in page jam
--Single buffer is used;
Buffer can understand the packaging of pair processing, for example, simple and easy to understand point:
   Workers move bricks
   The worker has 10000 bricks to move from area a to area B. he moves one brick at a time, 10000 times,
   In order not to run back and forth so many times, the workers figured out a way to find a basket to carry bricks. Each basket can carry 100 pieces,
   In this way, he can run back and forth 100 times to improve the efficiency of brick handling. So, this basket is a buffer processing.

It is also easy to understand the drawing of view. For example, we use the brush to draw multiple figures in order (with pause in the middle),
But view does not draw one by one, but in a draw method, all of them are drawn.
Because view also uses buffering.

SurfaceView   
--Can be refreshed in the child thread;
   It is not recommended if there is less content to draw, because creating threads and buffers also increases memory.
   On the contrary, it is recommended to use, but pay attention to thread control.   
--Double buffer is used;
   继续以Workers move bricks的例子讲解。
   The worker turns around and suddenly sees a truck (a truck can hold more than 10000 yuan), thinking it's not more convenient,
   So he first moved a frame of bricks to the car, and then drove the car to area B to unload the bricks.
   This car is equivalent to a second buffer.

To achieve double buffering when drawing a control, you can generally do this:
1. Create a new temporary picture and create its temporary canvas (the canvas is equivalent to that truck);
2. First draw the content we want to draw on the canvas of the temporary picture (that is, the picture)
3. When the control needs to be drawn, draw the picture to the real canvas of the control;
Through the above comparative analysis, we can draw a conclusion:
1. Full load chart (curve chart) can be drawn with view or surfaceview
  Because: the amount of information drawn is appropriate, and there is no special performance requirements.
2. For the charts (dynamic curves) loaded one by one, we try to use surfaceview to draw them
  Because: if the thread sleep is used in view to control the loading one by one, the main thread will be blocked
  (that is to say, the page looks at carton for half a day, and then suddenly draws out the effect after the block is restored.).
  If you don't want to get stuck, you can only use threads or timers in view to handle the itemized effect, and then communicate with the main thread.
  It's better to use surfaceview to refresh the view directly in the child thread instead of being so troublesome.

After reading the above introduction, I believe you should also understand the difference and usage between view and surfaceview.
Well, let’s go to the next step.


Design

The implementation of this function is relatively complex. We’d better have a simple layered or modular design for demo.
Analyze the structure of our demo, mainly including

  1. Two custom chart controls (view and surfaceview)
  2. Some simple business logic
  3. Data processing.

So, let’s use the existing framework directly. MVC and MVP are OK, but which one is better?
Let’s use MVP directly. Decoupling is better than MVC.
Instead of drawing an architecture diagram, let’s express it in text:

M (data layer):

1. Ichartdata.java chart data interface (provides a method to obtain chart data)
2. Chartdataimpl.java chart data implementation class (implements the above interface)
3. Chartdatainfo.java chart data entity class (encapsulates two properties: date and value)
4. Chartdateutils.java tool class (mainly dealing with date format)

P (presenter middle layer):

1. Chartpresenter.java is used to connect the M and V layers, and is responsible for the processing of business logic, which is to get the data and give it to the UI

V (UI level)

1. The ichartui.java UI interface provides a way to display charts for the presenter to use
2. The implementation class of mainactivity.java UI interface, which is used for the display and interaction of Graphs
3. The implementation class of surfacechartactivity.java UI interface is used for the display and interaction of dynamic curves
4. Chartview.java curve control (drawing directly with canvas and brush)
5. Chartsurfaceview.java dynamic graph control (drawing with timer, thread pool, thread, canvas and brush)
6. Drawchartutils.java drawing tool class (the drawn code is mainly encapsulated in this class)

代码结构图

How to realize the function has been designed, so, start the next step.
***

Realization

  1. Data layer
    The data layer mainly uses random numbers to simulate real data. There is no hard technical point. Let’s just paste the code
    1.1 chart data entity class
/**
 *Class: chartdatainfo chart data entity class
 *Author: QXC
 *Date: April 18, 2018
 */
public class ChartDataInfo {
    private String date;
    private int num;

    public ChartDataInfo(String date, int num) {
        this.date = date;
        this.num = num;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

1.2 chart data interface

import java.util.List;
/**
 *Class: ichartdata chart data interface
 *Author: QXC
 *Date: April 18, 2018
 */
public interface IChartData {
    /**
     *Get chart data
     *@ param size
     *@ return data set
     */
    List getChartData(int size);
}

1.3 chart data implementation class

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 *Class: chartdataimpl chart data implementation class
 *Author: QXC
 *Date: April 18, 2018
 */
public class ChartDataImpl implements IChartData{
    private int maxNum = 100;

    /**
     *Return random chart data
     *@ param size
     *@ return chart data set
     */
    @Override
    public List getChartData(int size) {
        List data = new ArrayList<>();
        Random random = new Random();
        random.setSeed(ChartDateUtils.getDateNow());
        //Returns a random number within maxnum
        for(int i = size-1; i>=0 ; i--){
            ChartDataInfo dataInfo = new ChartDataInfo(ChartDateUtils.getDate(i), random.nextInt(maxNum));
            data.add(dataInfo);
        }
        return data;
    }
}

1.4 data layer tools

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 *Class: dateutils data layer tool class
 *1. Date processing
 * 2.
 *Author: QXC
 *Date: April 18, 2018
 */
public class ChartDateUtils {
    public static long getDateNow(){
        Date date = new Date();
        return date.getTime();
    }

    public static String getDate(int day){
        Calendar calendar = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        calendar.add(Calendar.DATE, -day);
        String date = sdf.format(calendar.getTime());
        return date;
    }
}
  1. Presenter level
    This layer is the standard presenter, which holds the interfaces of M and V to process their business logic.
    2.1 ChartPresenter
import com.iwangzhe.mvpchart.model.ChartDataImpl;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import com.iwangzhe.mvpchart.model.IChartData;
import com.iwangzhe.mvpchart.view.IChartUI;

import java.util.List;

/**
 *Class: chartpresenter
 *Author: QXC
 *Date: April 18, 2018
 */
public class ChartPresenter {
    private IChartUI iChartView;
    private IChartData iChartData;

    public ChartPresenter(IChartUI iChartView) {
        this.iChartView = iChartView;
        this.iChartData = new ChartDataImpl();
    }

    //Get business logic of chart data
    public void getChartData(){
        //Number of data requested
        int size = 50;
        //Get chart data
        List data = iChartData.getChartData(size);
        //Set data to UI
        iChartView.showChartData(data);
    }
}
  1. UI layer (view)
    The technology of drawing is the core of this paper, which needs to be explained emphatically
    3.1 ichartui interface
package com.iwangzhe.mvpchart.view;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import java.util.List;
/**
 *Class: ichartview
 *Author: QXC
 *Date: April 18, 2018
 */
public interface IChartUI {
    /**
     *Show chart
     *@ param data
     */
    void showChartData(List data);
}

3.2 MainActivity
layout

Code

package com.iwangzhe.mvpchart.view;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import com.iwangzhe.mvpchart.R;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import com.iwangzhe.mvpchart.presenter.ChartPresenter;
import com.iwangzhe.mvpchart.view.customView.ChartView;

import java.util.List;

public class MainActivity extends Activity implements IChartUI {
    ChartPresenter chartPresenter;
    ChartView cv;
    Button btn;
    Button btnSurface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Initialize presenter
        chartPresenter = new ChartPresenter(this);
        //Initialize control
        initView();
        //Initialization data
        initData();
        //Initialize event
        initEvent();
    }

    //Initialize control
    private void initView() {
        cv = (ChartView) findViewById(R.id.cv);
        btn = (Button) findViewById(R.id.btn);
        btnSurface = (Button) findViewById(R.id.btnSurface);
    }

    //Initialization data
    private void initData() {
        Chartpresenter. Getchartdata(); // request data
    }

    //Initialize event
    private void initEvent() {
        //Refresh data
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Chartpresenter. Getchartdata(); // re request data (refresh data)
            }
        });
        //Jump to dynamic curve page
        btnSurface.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SurfaceChartActivity.class);
                startActivity(intent);
            }
        });
    }

    //Data callback of p layer
    @Override
    public void showChartData(List data) {       
        //Chart control set data source
        cv.setDataSet(data);
    }
}

3.3 SurfaceChartActivity
layout

Code

package com.iwangzhe.mvpchart.view;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.iwangzhe.mvpchart.R;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import com.iwangzhe.mvpchart.presenter.ChartPresenter;
import com.iwangzhe.mvpchart.view.customView.ChartSurfaceView;
import java.util.List;
/**
 *Class: surfacechartactivity
 *Author: QXC
 *Date: April 19, 2018
 */
public class SurfaceChartActivity extends Activity implements IChartUI{
    ChartPresenter chartPresenter;
    ChartSurfaceView cv;
    Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_surface_chart);
        //Initialize presenter
        chartPresenter = new ChartPresenter(this);
        //Initialize control
        initView();
        //Initialization data
        initData();
        //Initialize event
        initEvent();
    }

    //Initialize control
    private void initView() {
        cv = (ChartSurfaceView) findViewById(R.id.cv);
        btn = (Button) findViewById(R.id.btn);
    }

    //Initialization data
    private void initData() {
        Chartpresenter. Getchartdata(); // request data
    }

    //Initialize event
    private void initEvent() {
        //Refresh data
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Chartpresenter. Getchartdata(); // re request data (refresh data)
            }
        });
    }

    @Override
    public void showChartData(List data) {
        //Chart control set data source
        cv.setDataSource(data);
    }
}

3.4 ChartView

package com.iwangzhe.mvpchart.view.customView;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import java.util.List;
/**
 *Class: chartview
 *Author: QXC
 *Date: April 18, 2018
 */
public class ChartView extends View{
    Int canvaswidth; // Canvas width
    Int canvasheight; // Canvas height
    Int padding = 100; // boundary interval
    Paint paint; // brush

    List data; // data

    public ChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //Initialize brush properties
        initPaint();
    }

    //Set chart data
    public void setDataSet(List data){
        this.data = data;

        //Force redraw
        invalidate();
    }

    //Initialize brush properties
    private void initPaint(){
        //Set anti aliasing
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //Draw drawing styles
        //Paint.Style.STROKE stroke
        //Paint.style.fill content
        //Paint.style.fill'and'stroke content + stroke
        paint.setStyle(Paint.Style.STROKE);
        //Set brush width
        paint.setStrokeWidth(1);
    }

    //This method is called every time the appearance changes
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //Get canvas width
        this.canvasWidth = getWidth() - padding * 2;
        //Get canvas height
        this.canvasHeight = getHeight() - padding * 2;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //Chart information for each redraw
        DrawChartUtils.getInstance().drawChart(canvas, paint, canvasWidth,canvasHeight,padding,data);
    }
}
In this category,
1. Get the width and height of canvas in onsizechanged as the drawing area of background sideline and curve data
2. The width and height of the canvas subtract the padding information (both sides need to have padding, so multiplied by 2)
3. When the view is created, a brush is initialized and some properties of the brush are set
4. After the onsizechanged method is executed, the OnDraw method will be executed to draw. In this method, the canvas can be obtained
5. Every time the data is refreshed, after calling the setdataset method, the OnDraw method will also be forced to draw, because the invalidate method will force the redraw
6. We draw chart information in OnDraw method, and the drawing of chart information is encapsulated in drawchartutils class

3.5 ChartSurfaceView

package com.iwangzhe.mvpchart.view.customView;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *Class: chartsurfaceview
 *Author: QXC
 *Date: April 19, 2018
 */
public class ChartSurfaceView extends SurfaceView implements SurfaceHolder.Callback{
    SurfaceHolder holder;
    Timer timer;
    List data; // total data
    List showdata; // currently drawn data
    Executorservice ThreadPool; // thread pool

    Canvas canvas; // Canvas
    Paint paint; // brush
    Int canvaswidth; // Canvas width
    Int canvasheight; // Canvas height
    Int padding = 100; // boundary interval

    public ChartSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
        initPaint();
    }

    private void initView(){
        holder = getHolder();
        holder.addCallback(this);
        holder.setKeepScreenOn(true);
        ThreadPool = executors. Newcachedthreadpool(); // cache thread pool
    }

    //Initialize brush properties
    private void initPaint(){
        //Set anti aliasing
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //Draw drawing styles
        //Paint.Style.STROKE stroke
        //Paint.style.fill content
        //Paint.style.fill'and'stroke content + stroke
        paint.setStyle(Paint.Style.STROKE);
        //Set brush width
        paint.setStrokeWidth(1);
    }

    //Set chart data source
    public void setDataSource(List data){
        this.data = data;
        this.showData = new ArrayList<>();

        if(timer!=null){
            timer.cancel();
        }
        if(canvasWidth > 0){
            startTimer();
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        canvasWidth = getWidth() - padding * 2;
        canvasHeight = getHeight() - padding * 2;
        startTimer();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    }

    int index;
    private void startTimer(){
        index = 0;
        timer = new Timer();
        TimerTask task=new TimerTask() {
            @Override
            public void run() {
                index += 1;
                showData.clear();
                showData.addAll(data.subList(0,index));
                //Start sub thread drawing page and use thread pool management
                threadPool.execute(new ChartRunnable());
                if(index>=data.size()){
                    timer.cancel();
                }
            }
        };
        timer.schedule(task, 0 , 20);
    }

    // Zi Xiancheng
    class ChartRunnable implements Runnable{
        @Override
        public void run() {
            //Get canvas
            canvas = holder.lockCanvas();
            //Drawing curves
            DrawChartUtils.getInstance().drawChart
             (canvas,paint,canvasWidth,canvasHeight,padding,showData);
            //Submit canvas
            holder.unlockCanvasAndPost(canvas);
        }
    }
}
The main difference between this class and chartview is that the graphics are drawn in a sub thread
The same thing will not be repeated here, but mainly about the difference:
1. Need to implement surfaceholder.callback and rewrite 3 methods
  Surfacecreated when the view is created successfully, it will trigger, indicating that the drawing work can be done
  When the view changes, the surface changed will be triggered. In general, the data parameters can be reassigned;
  Surfacetestroyed will be triggered when view is destroyed. Generally, some pre destruction processing work, such as threads, will be done
2. Here, the load by load is implemented by timer. Each timer cycle, an additional piece of data is added to the collection,
  At the same time, create a thread to draw once. When all data are drawn, cancel timer;
3. With timer, a thread is created in each cycle, so we need to improve the efficiency and use the cache thread pool to control the thread;
4. The way to get the canvas in surfaceview is different from that in view
  View is obtained directly in the OnDraw method
  Surfaceview is obtained through holder. Lockcanvas(). After drawing, you must submit:
  holder.unlockCanvasAndPost(canvas);
  Otherwise, the page is stuck.

3.6 DrawChartUtils

package com.iwangzhe.mvpchart.view.customView;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import com.iwangzhe.mvpchart.model.ChartDataInfo;
import java.util.List;
/**
 *Class: chartutils
 *Author: QXC
 *Date: April 19, 2018
 */
public class DrawChartUtils {
    Private canvas; // Canvas
    Private paint paint; // brush
    Private int canvas width; // Canvas width
    Private int canvas height; // Canvas height
    Private int padding; // view boundary interval

    Private final string color "=" 343643 "; // background color
    Private final string color? BG? Line = "? 999dd2"; // background color
    Private final string color line = "FF"; // line color
    Private final string color? Text = "? Ffffff"; // text color

    List showdata; // chart data

    private static DrawChartUtils chartUtils;
    public static DrawChartUtils getInstance(){
        if(chartUtils == null){
            synchronized (DrawChartUtils.class){
                if(chartUtils == null){
                    chartUtils = new DrawChartUtils();
                }
            }
        }
        return chartUtils;
    }

    //Charting
    public void drawChart(Canvas canvas, Paint paint, int canvasWidth, int canvasHeight, int padding, List showData) {
        //Initialize canvas, brush and other data
        this.canvas = canvas;
        this.paint = paint;
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.padding = padding;
        this.showData = showData;
        if(canvas == null || paint==null || canvasWidth<=0 ||canvasHeight<=0||showData==null || showData.size() ==0){
            return;
        }

        //Charting背景
        drawBg();
        //Charting线
        drawLine();
    }

    //Charting背景
    private void drawBg(){
        //Draw background color
        canvas.drawColor(Color.parseColor(color_bg));

        //Draw background axis
        drawBgAxisLine();
    }

    //Charting背景坐标轴线
    private void drawBgAxisLine(){
        //5 lines: 5 lines are drawn horizontally and vertically
        int lineNum = 5;
        Path path = new Path();

        //x. Y-axis interval
        int x_space = canvasWidth / lineNum;
        int y_space = canvasHeight / lineNum;

        // draw the horizontal line.
        for(int i=0; i<=lineNum; i++){
            path.moveTo(0 + padding, i * y_space+ padding);
            path.lineTo(canvasWidth+ padding, i * y_space+ padding);
        }

        // draw the longitudinal line.
        for(int i=0; i<=lineNum; i++){
            path.moveTo(i * x_space+ padding, 0 + padding);
            path.lineTo(i * x_space+ padding, canvasHeight+ padding);
        }

        //Set brush width, style, color
        paint.setStrokeWidth(2);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor(color_bg_line));
        // painting path
        canvas.drawPath(path, paint);
    }

    //Charting线(数据曲线)
    private void drawLine(){
        if(showData == null){
            return;
        }
        int size = showData.size();

        //Canvas adaptive display data (that is, the width of the canvas should display a full amount of chart data)
        //x axis spacing
        float x_space = canvasWidth / size;
        //The maximum and minimum value range of Y axis corresponds to the height of canvas (that is, the height of canvas should display the full amount of chart data)
        float max = getMaxData();
        float min = getMinData();

        float pre_x = 0;
        float pre_y = 0;
        Path path = new Path();

        //Draw left to right
        //Convert the value to the corresponding coordinate value
        for(int i=0; imax?info.getNum():max;
        }
        return max;
    }

    //Get the minimum value: used to calculate and fit the y-axis interval
    private int getMinData(){
        int min = showData.get(0).getNum();
        for(ChartDataInfo info : showData){
            min = info.getNum()
This class is a drawing tool class, only including the drawing method, and parameters such as canvas and brush need to be passed in from outside
1. GetInstance method to obtain the singleton of this class (thread safe singleton)
2. Drawchart method is an external drawing entry method
  Receive external transmission and judge the validity
  Call the method of drawing chart background
  Call the method of drawing chart line
3. Drawbg, the method of drawing background, including two parts: background color and background frame
  The background color is a direct fill without a brush
4. Drawbgaxisline, draw the background border line
  Draw 5 + 1 horizontal lines and 1 vertical line respectively. Each line can be considered as the path of the brush,
  Then, we can encapsulate each path and put it into the collection.
  We don't need to define such a collection ourselves, just use the path provided by the system
  Path has several common methods:  
  Moveto (float DX, float dy) moves directly to a certain point, and there is no connection in the middle;
  Lineto (float DX, float dy) uses a straight line to connect to a point;
  Quadto (float dx1, float DY1, float DX2, float dy2) uses a curve to connect to a point (Bezier curve);
  CubicTo(float x1,float y1,float x2,float y2,float x3,float y3)
  Use curve to connect to a point with more parameters;
5. There are many ways to set the brush. Here are only the ones we use
  Paint = new paint (paint. Anti ﹣ alias ﹣ flag); anti aliasing, if not set, the interface is rough and has the effect of aliasing;
  Paint.setstrokewidth (2); sets the width of the stroke
  paint.setStyle(STROKE);
  Setting style, mainly including solid, stroke, solid and stroke three types, draw line is generally set to stroke;
  Paint. SetColor (color. Parsecolor (color BG line)); // set color
6. DrawLine draws the curve, mainly corresponding the data (set index and value size) to the coordinates of the coordinate system
  The x-axis bisects the x-axis length according to the subscript of the set;
  Y-axis locates the position of the value according to the maximum and minimum value;
  Path is still used for drawing lines, which is more suitable than drawing each curve separately;
7. Draw text
  paint.setStyle(Paint.Style.FILL);
  The brush can be adjusted to be solid and the drawing text is more beautiful. Of course, it can also be of other types. Please adjust it according to your preference;
  float width_text = paint.measureText(end);
  By setting the brush parameters and text content, the actual width of the text can be calculated accurately by using the measuretext method of the brush;
  The coordinates of the text are different from other graphics. The drawing position is based on the text baseline,
  When drawing the curve text, the text position is not accurately processed;
  When the date is drawn, the text position is precisely processed;
  float y_start = canvasHeight + padding - paint.descent() - paint.ascent() +10;
  For more precise text location control, please refer to the article: https://www.jianshu.com/p/3e48dd0547a0

summary

There are many technical points involved in this sharing. Let’s briefly review them:
–Application of MVP framework;
–Custom view implementation chart;
–Custom surface view implementation chart;
–The main difference between view and surfaceview and the difference of using scene;
–Use of canvas, brush, path and other drawing classes;
–Application of timer, runnable and thread pool;

The thinking of other kinds of graphics is basically the same.
If you want to interact with chart controls, such as data drag, touch, zoom, slide positioning and other special effects, you need to learn more about event transfer interaction mechanism, gesturedetector, scalegesturedetector and other technologies.
If you have time in the future, you can also give us a detailed introduction.

The download address of this demo: https://pan.baidu.com/s/1jm8lyryoyovos_iylz4dra
Because of the time relationship, demo didn’t do a particularly detailed test. If you have any problems, please adjust yourself.