I originally wanted to put the canvas operation in the later part, but I found that many graphics rendering are inseparable from the canvas operation, so I'll explain the basic operation methods of the canvas first.
1, Quick reference table for common operation of Canvas
Operation type | Related API | remarks |
---|---|---|
Draw color | drawColor, drawRGB, drawARGB | Fill the entire canvas with a single color |
Draw basic shape | drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc | Point, line, rectangle, rounded rectangle, ellipse, circle and arc |
Draw picture | drawBitmap, drawPicture | Draw bitmaps and pictures |
Draw text | drawText, drawPosText, drawTextOnPath | Draw text successively, specify each text position when drawing text, and draw text according to the path |
Draw path | drawPath | This function is also needed to draw paths and Bezier curves |
vertex operations | drawVertices, drawBitmapMesh | The image can be deformed by operating on vertices. drawVertices directly acts on the canvas and drawBitmapMesh only acts on the drawn Bitmap |
Canvas clipping | clipPath, clipRect | Sets the display area of the canvas |
Canvas snapshot | save, restore, saveLayerXxx, restoreToCount, getSaveCount | Save the current state, roll back to the last saved state, save the layer state, roll back to the specified state, and obtain the number of saves |
Canvas transformation | translate, scale, rotate, skew | They are displacement, scaling, rotation and staggered cutting |
Matrix | getMatrix, setMatrix, concat | In fact, the displacement, scaling and other operations of the canvas are all image Matrix, but the Matrix is difficult to understand and use, so it encapsulates some common methods. |
2, Basic operation of Canvas
1. Canvas operation
Why do I need canvas operation?
Canvas manipulation can help us make graphics in a more understandable way.
For example, how to draw a line segment with a length of 20dp and an angle of 30 degrees with the horizontal line from the coordinate origin?
According to our usual idea (the mathematical thinking that has been trained all the year round), we first use the trigonometric function to calculate the coordinates of the end point of the line segment, and then call drawLine.
However, is this confined by inherent thinking?
Suppose we first draw a horizontal line with a length of 20dp, and then rotate the horizontal line by 30 degrees, the final effect looks the same, and there is no need to calculate the trigonometric function. Is it a little simpler?
Reasonable use of canvas operation can help you create the effect you want in a more understandable way, which is also the reason for the existence of canvas operation.
PS: all canvas operations only affect the subsequent painting, and have no impact on the previously painted content.
(1) displacement (translate)
translate is the movement of the coordinate system. You can select an appropriate coordinate system for drawing. Note that the displacement is based on the current position, not each time based on the (0,0) point in the upper left corner of the screen, as follows:
// The code to create the brush is omitted // Draw a black circle at the coordinate origin mPaint.setColor(Color.BLACK); canvas.translate(200,200); canvas.drawCircle(0,0,100,mPaint); // Draw a blue circle at the coordinate origin mPaint.setColor(Color.BLUE); canvas.translate(200,200); canvas.drawCircle(0,0,100,mPaint);
We first move the coordinate system a distance to draw a circle, and then move a distance to draw a circle. The two movements can be superimposed.
(2) scale
Scaling provides two methods, as follows:
public void scale (float sx, float sy) public final void scale (float sx, float sy, float px, float py)
The first two parameters in the two methods are the same, which are the scale of x-axis and y-axis respectively. The second method has two more parameters than the previous one to control the position of the zoom center.
Detailed explanation of the value range of scaling (sx,sy):
Value range (n) | explain |
---|---|
(-∞, -1) | Zoom in n times according to the zoom center, and then flip according to the central axis |
-1 | Flip according to the scale center axis |
(-1, 0) | First zoom out to n according to the zoom center, and then flip according to the central axis |
0 | It will not be displayed. If sx is 0, the width is 0 and will not be displayed. The same is true for sy |
(0, 1) | Zoom out to n according to the zoom center |
1 | No change |
(1, +∞) | Zoom in n times according to the zoom center |
If you pay a little attention during scaling, you will find that the scaling center is the coordinate origin by default, and the scaling center axis is the coordinate axis, as follows:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.scale(0.5f,0.5f); // Canvas zoom mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
(in order to be more intuitive, I added a coordinate system. It can be seen that the scaling center is the coordinate origin)
Next, we use the second method to change the zoom center position slightly, as follows:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.scale(0.5f,0.5f,200,0); // Canvas zoom < -- the zoom center is offset by 200 units to the right mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
(the arrow in the figure indicates the zoom center.)
The scaling values of the previous two examples are positive numbers. According to the instructions in the table, when the scaling scale is negative, it will be flipped according to the scaling center axis. Let's experiment:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.scale(-0.5f,-0.5f); // Canvas zoom mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
For obvious effect, this time I not only added the coordinate system, but also marked several important points in the rectangle. The points marked with the same letter correspond to each other one by one.
Since the scaling center is not offset this time, all default scaling centers are the coordinate origin, and the central axis is the x-axis and y-axis.
This scaling can be seen as first scaling to 0.5 times the original according to the scaling Center (coordinate origin), and then flipping according to the x-axis and y-axis respectively.
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.scale(-0.5f,-0.5f,200,0); // Canvas zoom < -- the zoom center is offset by 200 units to the right mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
So many auxiliary contents have been added. I hope you can understand them.
This time, the y-axis coordinates of the scaling center point are offset, so the center axis is also offset to the right.
PS: like translate, scaling can be superimposed.
canvas.scale(0.5f,0.5f); canvas.scale(0.5f,0.1f);
Call scaling twice, then the actual scaling of X axis is 0.5x0.5 = 0.25, and the actual scaling of Y axis is 0.5x0.1=0.05
Let's use this feature to make an interesting figure.
Note: set the brush mode to stroke
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(-400,-400,400,400); // Rectangular area for (int i=0; i<=20; i++) { canvas.scale(0.9f,0.9f); canvas.drawRect(rect,mPaint); }
(3) rotate
Rotation provides two methods:
public void rotate (float degrees) public final void rotate (float degrees, float px, float py)
Like scaling, the two additional parameters of the second method still control the rotation center point.
The default rotation center is still the coordinate origin:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.rotate(180); // Rotate 180 degrees < -- the defau lt rotation center is the origin mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
Change the rotation center position:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,-400,400,0); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.rotate(180,200,0); // Rotate 180 degrees < -- the center of rotation is offset 200 units to the right mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
Well, rotation can also be superimposed
canvas.rotate(180); canvas.rotate(20);
Call two rotations, and the actual rotation angle is 180 + 20 = 200 degrees.
In order to demonstrate this effect, I made something that I didn't know was fierce:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); canvas.drawCircle(0,0,400,mPaint); // Draw two circles canvas.drawCircle(0,0,380,mPaint); for (int i=0; i<=360; i+=10){ // Draw a connecting line between circles canvas.drawLine(0,380,0,400,mPaint); canvas.rotate(10); }
(4) skew
Skew is translated here as skew, which is a special type of linear transformation.
Staggered cutting only provides one method:
public void skew (float sx, float sy)
Parameter meaning: float sx: tilt the canvas at the corresponding angle in the x direction, sx the tan value of the tilt angle, float sy: tilt the canvas at the corresponding angle in the y axis direction, sy is the tan value of the tilt angle
After transformation:
X = x + sx * y Y = sy * x + y
Example:
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,0,200,200); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.skew(1,0); // Horizontal stagger < - 45 degrees mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
As you think, the staggered cut can also be superimposed, but please note that the drawing results will be different with different call order
// Move the coordinate system origin to the center of the canvas canvas.translate(mWidth / 2, mHeight / 2); RectF rect = new RectF(0,0,200,200); // Rectangular area mPaint.setColor(Color.BLACK); // Draw a black rectangle canvas.drawRect(rect,mPaint); canvas.skew(1,0); // Horizontal stagger canvas.skew(0,1); // Vertical stagger mPaint.setColor(Color.BLUE); // Draw a blue rectangle canvas.drawRect(rect,mPaint);
(5) snapshot (save) and rollback (restore)
Q: Why do snapshots and rollback exist
A: Canvas operations are irreversible, and many canvas operations will affect subsequent steps. For example, in the first example, two circles are drawn at the coordinate origin, and the actual positions drawn are different due to the movement of the coordinate system. Therefore, some states of the canvas will be saved and rolled back.
Related API s:
Related API | brief introduction |
---|---|
save | Save the state of the current canvas and put it into a specific stack |
saveLayerXxx | Create a new layer and put it into a specific stack |
restore | Take out the top canvas state in the stack and restore the current canvas according to this state |
restoreToCount | Pop up the status of the specified location and all above, and restore according to the status of the specified location |
getSaveCount | Get the number of contents in the stack (i.e. save times) |
Some of these concepts and methods are analyzed below:
Status stack:
In fact, I don't know the name of this stack. It's temporarily called the status stack. It looks like the following:
This stack can store canvas state and layer state.
Q: What are canvases and layers?
A: In fact, the canvas we see is composed of multiple layers, as shown in the following figure (the picture is from the network):
In fact, the painting and canvas operations we explained earlier are carried out on the default layer.
In general, the default layer can meet the requirements, but if it is necessary to draw more complex content, such as a map (the map can be superimposed by multiple map layers, such as administrative region layer, road layer and point of interest layer), it is better to draw by layer.
You can think of these layers as layers of glass plates. You draw the content on each layer of glass plates, and then stack these glass plates together to see the final effect.
SaveFlags
name | brief introduction |
---|---|
ALL_SAVE_FLAG | By default, all States are saved |
CLIP_SAVE_FLAG | Save clip area |
CLIP_TO_LAYER_SAVE_FLAG | The clipping area is saved as a layer |
FULL_COLOR_LAYER_SAVE_FLAG | Saves all color channels of a layer |
HAS_ALPHA_LAYER_SAVE_FLAG | Saves the alpha (opacity) channel of the layer |
MATRIX_SAVE_FLAG | Save Matrix information (translate, rotate, scale, skew) |
save
There are two ways to save:
// Save all status public int save () // Save part of the status according to the saveFlags parameter public int save (int saveFlags)
You can see that the second method has one more saveFlags parameter than the first method. Using this parameter can save only part of the state, which is more flexible. For details of this saveFlags parameter, please refer to the table above.
Every time the save method is called, a status message will be added at the top of the stack. Take the above status stack image as an example. Calling save again will add a status message on the fifth time.
saveLayerXxx
saveLayerXxx has many methods:
// No layer alpha channel public int saveLayer (RectF bounds, Paint paint) public int saveLayer (RectF bounds, Paint paint, int saveFlags) public int saveLayer (float left, float top, float right, float bottom, Paint paint) public int saveLayer (float left, float top, float right, float bottom, Paint paint, int saveFlags) // There is a layer alpha (opacity) channel public int saveLayerAlpha (RectF bounds, int alpha) public int saveLayerAlpha (RectF bounds, int alpha, int saveFlags) public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha) public int saveLayerAlpha (float left, float top, float right, float bottom, int alpha, int saveFlags)
Note: the saveLayerXxx method will make you spend more time rendering the image (the superposition of more layers will lead to the doubling of the calculation). Please be careful before using it. If possible, try to avoid using it.
Using the saveLayerXxx method, the layer state will also be put into the state stack. Similarly, use the restore method to restore.
This is not enough for the time being. If you need to explain it in detail later. (because there are many things in it, too, QAQ)
restore
State rollback is to take a state from the top of the stack and restore it according to the content.
Similarly, take the above state stack image as an example. Call the restore method once to take out the state stack for the fifth time and restore the state according to the saved state.
restoreToCount
Pop up the specified location and all the above states, and recover according to the specified location state.
Take the above status stack picture as an example. If restoreToCount(2) is called, the status of 2, 3, 4 and 5 will pop up and be restored according to the status saved for the second time.
getSaveCount
Get the number of saved times, that is, the number of saved states in the state stack. Take the above state stack picture as an example, and the return value of this function is 5.
However, please note that the minimum return value of this function is 1. Even if all statuses pop up, the return value is still 1, representing the default status.
Common formats
Although there is a lot of verbosity about state saving and rollback, in most cases, you only need to remember the following steps:
save(); //Save status ... //Specific operation restore(); //Rollback to previous state
This method is also the simplest and easiest to understand.
3, Summary
As mentioned at the beginning of this article, reasonable use of canvas operations can help you create the effects you want in a more understandable way.