Drawing Basic Shape of Android Custom View( V)_Canvas

Keywords: Android snapshot

In this article, we will introduce the use of Canvas.

Introduction to Canvas

Canvas, which we can call canvas, can draw all kinds of things on it. It's the foundation of 2D graphics rendering on Android platform. It's very powerful.

Generally speaking, there are two main characteristics of the more basic things:
1. Strong operability: Because these are the basis of the upper layer, so operability is bound to be very strong.  
2. Difficult to use: various methods are too basic, it is difficult to combine these operations perfectly.

But don't worry, this series of articles will not only introduce the operation of Canvas, but also briefly introduce some design ideas and techniques.

II. Quick Lookup Table of Common Operation of Canvas

Operation type Related API Remarks
Color rendering drawColor, drawRGB, drawARGB Fill the entire canvas with a single color
Draw basic shapes drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc The order is point, line, rectangle, rounded rectangle, ellipse, circle and arc.
Draw pictures drawBitmap, drawPicture Drawing bitmaps and pictures
Draw text drawText, drawPosText, drawTextOnPath Drawing text, specifying the location of each text when drawing text, and drawing text according to the path in turn
Draw path drawPath This function is also needed to plot paths and Bessel curves.
vertex operations drawVertices, drawBitmapMesh By manipulating vertices, images can be deformed. DraVertices directly act on canvas and DraBitmapMesh only works on Bitmap.
Canvas cut clipPath, clipRect Set the display area of the canvas
Canvas snapshot save, restore, saveLayerXxx, restoreToCount, getSaveCount The order is to save the current state, rollback to the last saved state, save the layer state, rollback to the specified state, and get the number of saved times.
Canvas transformation translate, scale, rotate, skew The order is displacement, zooming, rotation and staggering.
Matrix (Matrix) getMatrix, setMatrix, concat The displacement and scaling of the actual canvas are all based on the image matrix Matrix, but Matrix is difficult to understand and use, so it encapsulates some commonly used methods.

PS: The common methods of Canvas are listed in the table above. Of course, there are some other methods that are not listed. You can refer to the official documents specifically. Canvas

III. Canvas Explanation

This article mainly explains how to use Canvas to draw basic graphics.

Draw color:

Drawing color is to fill the whole canvas, often used to draw background color.

  canvas.drawColor(Color.BLUE); //Drawing blue
  • 1

For more information on colours, please refer to Android custom View(3)_color

Create brushes:

To draw content, you first need to create a brush, as follows:

  // 1. Create a Brush
  private Paint mPaint = new Paint();

  // 2. Initialization Brush
    private void initPaint() {
        mPaint.setColor(Color.BLACK);       //setpc
        mPaint.setStyle(Paint.Style.FILL);  //Set Brush Mode to Fill
        mPaint.setStrokeWidth(10f);         //Set the brush width to 10px
    }

  // 3. Initialization in constructors
    public SloopView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

After creating the brush, you can draw various contents in Canvas.

Draw points:

You can draw a point or a set of points as follows:

        canvas.drawPoint(200, 200, mPaint);     //Draw a point at coordinates (200,200)
        canvas.drawPoints(new float[]{          //Draw a set of points whose coordinate positions are specified by float arrays
                500,500,
                500,600,
                500,700
        },mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

As for the origin of coordinates, it defaults to be in the upper left corner, increasing direction of x axis from horizontal to right, and increasing direction of y axis from vertical to downward.

See more here. Android Custom View(1)_ Coordinate System

Draw a straight line:

Drawing a straight line requires two points, the initial point and the end point. Similarly, drawing a straight line can also draw one or a group of points:

 canvas.drawLine(300,300,500,600,mPaint);    // Draw a straight line between coordinates (300,300) (500,600)
        canvas.drawLines(new float[]{               // Draw a set of lines every four digits (coordinates of two points) to determine a line
                100,200,200,200,
                100,300,200,300
        },mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5

Draw a rectangle:

Determining a rectangle requires at least four data, that is, the coordinates of two points of the diagonal line, where the coordinates of the two points in the upper left corner and the lower right corner are generally used.

Canvas provides three overloading methods for drawing rectangles. The first is to provide four values (the coordinates of two points in the upper left corner and the lower right corner of the rectangle) to determine a rectangle for drawing. The other two are encapsulated as Rect or RectF (which is still a rectangle determined by two coordinate points) and then passed to Canvas for drawing, as follows:

        // First kind
        canvas.drawRect(100,100,800,400,mPaint);

        // Second kinds
        Rect rect = new Rect(100,100,800,400);
        canvas.drawRect(rect,mPaint);

        // Third kinds
        RectF rectF = new RectF(100,100,800,400);
        canvas.drawRect(rectF,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

The results of the above three methods are exactly the same.  

Seeing this, I believe that many audiences will have a question, why are there Rect and RectF? Is there any difference between the two?

The answer, of course, is different. The biggest difference between them is the accuracy. Rect is int, and RectF is float. In addition to the difference in accuracy, there are slight differences between the two methods.

Draw a rounded rectangle:

Drawing rounded rectangles also provides two overloading methods, as follows:

 // First kind
        RectF rectF = new RectF(100,100,800,400);
        canvas.drawRoundRect(rectF,30,30,mPaint);

        // Second kinds
        canvas.drawRoundRect(100,100,800,400,30,30,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

The effect of the above two methods is the same, but since the second method is added at API 21, we usually use the first method.  

The following is a brief analysis of the meaning of several necessary parameters of a rounded rectangle.

Obviously, the first four parameters of the second method are the same as the Rotf of the first method. They are all for determining a rectangle. The last parameter Paint is a brush. Needless to say, compared with rectangle, there are two parameters rx and ry in rounded rectangle. What are these two parameters?

After a little analysis, since it is a rounded rectangle, its angle must be an arc (part of a circle). What do we usually use to determine a circle?

The answer is the center and radius, where the center is used to determine the position and the radius is used to determine the size.

Because the rectangle position has been determined, so its edge and corner position is also determined, then the parameters of determining the position can be omitted, and only the radius can be used to describe an arc.

But radius only needs one parameter, but how can there be two here?

Well, let's find out that the corner of the corner rectangle here is not actually an arc of a regular circle, but an arc of an ellipse. The two parameters here are actually two radii of the ellipse. They look like the following:
 
The red line labeled rx and ry are two radii, which are the two parameters that are more than the rectangle drawn.

When we understand the principle, we can do whatever we want. By calculating, we know that the width of the rectangle we drew last time is 700 and the height is 300. When you make rx greater than 350 (half of the width) and ry greater than 150 (half of the height), the miracle appears. You will find that the rounded rectangle becomes an ellipse. They draw it like this (to confirm that I changed the color of the brush, Rectangles and rounded rectangles are drawn at the same time:

  // rectangle
        RectF rectF = new RectF(100,100,800,400);  

        // Draw Background Rectangle
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(rectF,mPaint);

        // Draw rounded rectangle
        mPaint.setColor(Color.BLUE);
        canvas.drawRoundRect(rectF,700,400,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10


The gray part is the rectangle we selected, and the round rectangle inside becomes an ellipse. In fact, when rx is half of the width and ry is half of the height, it is just an ellipse. It can be calculated by the principle of above analysis. When rx is more than half of the width and ry is more than half of the height, it is impossible to calculate the arc. DraRoundRect is used to restrict (modify) parameters larger than this value, and all parameters larger than half are treated as half.

Draw an ellipse:

It is much simpler to draw an ellipse than to draw a rounded rectangle, because he only needs a rectangular rectangle as a parameter:

        // First kind
        RectF rectF = new RectF(100,100,800,400);
        canvas.drawOval(rectF,mPaint);

        // Second kinds
        canvas.drawOval(100,100,800,400,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Similarly, the effect of the above two methods is exactly the same, but the first method is generally used.  

Drawing an ellipse is actually drawing an inner tangent of a rectangle. The principle is as follows, let alone say:
 
PS: If you pass in a rectangle of equal length and width (that is, a square), then the drawing is actually a circle.

Draw circles:

Drawing a circle is also relatively simple, as follows:

  canvas.drawCircle(500,500,400,mPaint);  // Draw a circle with a center coordinate at (500,500) and a radius of 400.
  • 1

Drawing a circle has four parameters, the first two are the coordinates of the center of the circle, the third is the radius, and the last is the brush.  

Draw an arc:

Drawing an arc is a bit magical. In order to understand this magical thing, let's first look at the parameters it needs:

// First kind
public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}

// Second kinds
public void drawArc(float left, float top, float right, float bottom, float startAngle,
            float sweepAngle, boolean useCenter, @NonNull Paint paint) {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

As can be seen from the above, there are three more parameters for drawing an arc than for drawing an ellipse.

startAngle  // Starting angle
sweepAngle  // Sweep angle
useCenter   // Whether to use the center or not
  • 1
  • 2
  • 3
  • 4

Literally, we can almost guess the function of the first two parameters (start Angle, sweep Angel) is to determine the starting position and sweep angle of the angle, but what is the third parameter? Just try it out. Code it:

       RectF rectF = new RectF(100,100,800,400);
        // Draw Background Rectangle
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(rectF,mPaint);

        // Arc
        mPaint.setColor(Color.BLUE);
        canvas.drawArc(rectF,0,90,false,mPaint);

        //-------------------------------------

        RectF rectF2 = new RectF(100,600,800,900);
        // Draw Background Rectangle
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(rectF2,mPaint);

        // Arc
        mPaint.setColor(Color.BLUE);
        canvas.drawArc(rectF2,0,90,true,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

The above code actually draws an arc with a starting angle of 0 degrees and a sweep of 90 degrees. The difference between the two is whether the center point is used or not. The results are as follows:

It can be found that when the central point is used, the drawing is similar to a sector, but when the central point is not used, the line between the starting point and the ending point of the arc and the figure surrounded by the arc are added. In this way, the function of the central point parameter is obvious. It is not necessary to say that you will understand it after a try. In addition, you can refer to this article about the angle: Android Custom View(2)_ Angle and Radius 
Compared with ellipses, we still use more positive circles. We use positive circles to show the effect.

        RectF rectF = new RectF(100,100,600,600);
        // Draw Background Rectangle
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(rectF,mPaint);

        // Arc
        mPaint.setColor(Color.BLUE);
        canvas.drawArc(rectF,0,90,false,mPaint);

        //-------------------------------------

        RectF rectF2 = new RectF(100,700,600,1200);
        // Draw Background Rectangle
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(rectF2,mPaint);

        // Arc
        mPaint.setColor(Color.BLUE);
        canvas.drawArc(rectF2,0,90,true,mPaint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Brief introduction to Paint

Looking at the above so many, I believe that some people will have a question, if I want to draw a circle, as long as the edge does not have the color inside?

Simply, the basic shape of the drawing is determined by Canvas, but the color of the drawing is determined by Paint.

If you notice, at the beginning we set the brush style like this:

  mPaint.setStyle(Paint.Style.FILL);  //Set Brush Mode to Fill
  • 1

In order to display conveniently and see the effect easily, the previous mode has always been filling mode. In fact, the brush has three modes, as follows:

STROKE                //Stroke
FILL                  //Fill
FILL_AND_STROKE       //Drawing edge and filling
  • 1
  • 2
  • 3

In order to distinguish the three effects, we do the following experiments:

        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(40);     //In order to achieve obvious experimental results, the width of the stroke edge is specially set up to be very large.

        // Stroke
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(200,200,100,paint);

        // Fill
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(200,500,100,paint);

        // Drawing edge and filling
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawCircle(200, 800, 100, paint);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15


One picture is worth thousands of words. Through the above experiments, we can see the difference between the three modes more clearly. If only the edge does not need filling content, we only need to set the mode to STROKE.

In fact, there are many things about Paint. These are just the tip of the iceberg. Paint will be explained in detail in the following content.

Small example

Briefly introduce the operation of canvas:

Canvas operation details will be explained in the next article, not the focus of this article, but may be used in the following examples, so here is a brief introduction.

Related operations Brief introduction
save Save the current canvas status
restore Roll back to the last saved state
translate Displacement relative to current position
rotate rotate

Make a pie chart

Pie charts are often used when displaying percentage data, such as:

Simple analysis

In fact, according to our knowledge above, we can already make a pie chart by ourselves. But the most important thing in making things is not the result, but the idea of making things. Believe that I pasted the code, you immediately understand, very simple things. However, we still want to know about the production ideas:

First, the composition of the pie chart is analyzed. It is very obvious that the pie chart is composed of one sector after another. Each sector has different colors, corresponding to the name, data and percentage.

From the above information, it can be concluded that the basic data of pie chart should include: the angle color corresponding to the percentage of name data value.

Data that users care about: Percentage of Name Data Value
Data requiring program calculation: the angle of percentage
Among them, the item of color can be specified either by user or by program (we use program to specify here).

Encapsulated data:

public class PieData {
    // Users care about data
    private String name;        // Name
    private float value;        // numerical value
    private float percentage;   // Percentage

    // Non-users care about data
    private i`Write code slices here.`nt color = 0;      // colour
    private float angle = 0;    // angle

    public PieData(@NonNull String name, @NonNull float value) {
        this.name = name;
        this.value = value;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

PS: The get set method is omitted above.

Custom View:

Following the custom View process, sort it out (determine what each step should do):

step Keyword Effect
1 Constructor Initialization (Initialization Brush Paint)
2 onMeasure Measure the size of the View (not to be concerned for the time being)
3 onSizeChanged Determine the View size (record the width of the current View)
4 onLayout Determine the child View layout (no child View, no concern)
5 onDraw Actual drawing content (pie chart)
6 Provide interface Provide interfaces (provide interfaces for setting data)

The code is as follows:

public class PieView extends View {
    // Color table (Note: The color defined here uses ARGB with Alpha channel)
    private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
            0xFFE6B800, 0xFF7CFC00};
    // Initial drawing angle of pie chart
    private float mStartAngle = 0;
    // data
    private ArrayList<PieData> mData;
    // Width height
    private int mWidth, mHeight;
    // Paint brush
    private Paint mPaint = new Paint();

    public PieView(Context context) {
        this(context, null);
    }

    public PieView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mData)
            return;
        float currentStartAngle = mStartAngle;                    // Current Starting Angle
        canvas.translate(mWidth / 2, mHeight / 2);                // Move the origin of the canvas coordinates to the central position
        float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);  // Radius of pie chart
        RectF rect = new RectF(-r, -r, r, r);                     // Cake plotting area

        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);
            mPaint.setColor(pie.getColor());
            canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
            currentStartAngle += pie.getAngle();
        }

    }

    // Set the starting angle
    public void setStartAngle(int mStartAngle) {
        this.mStartAngle = mStartAngle;
        invalidate();   // Refresh
    }

    // Setting data
    public void setData(ArrayList<PieData> mData) {
        this.mData = mData;
        initData(mData);
        invalidate();   // Refresh
    }

    // Initialization data
    private void initData(ArrayList<PieData> mData) {
        if (null == mData || mData.size() == 0)   // Direct return of data with problems
            return;

        float sumValue = 0;
        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);

            sumValue += pie.getValue();       //Computational numerical sum

            int j = i % mColors.length;       //Set color
            pie.setColor(mColors[j]);
        }

        float sumAngle = 0;
        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);

            float percentage = pie.getValue() / sumValue;   // Percentage
            float angle = percentage * 360;                 // Corresponding angles

            pie.setPercentage(percentage);                  // Percentage of records
            pie.setAngle(angle);                            // Recording Angle Size
            sumAngle += angle;

            Log.i("angle", "" + pie.getAngle());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

PS: The invalidate() function is called to redraw the data when it needs to redraw the interface.  
Effect picture

PS: This pie chart does not add data such as percentages. It is only used as an example.  

Posted by buckboru on Thu, 16 May 2019 16:20:46 -0700