 # By the end of this article Canvas you should be able to

Posted on Sept. 23, 2022, 9 p.m. by Joel Monroe
Category: android Tag: android

## preface

The previous article introduced the API and advanced use of Paint. Next, we will talk about the knowledge of Canvas drawing. Canvas can implement many drawing methods, and this article will introduce the use of Canvas.

Today, I found a way to explain customization. You can read the original text of Android about Canvas what you know and don't know, which is explained in the way of coordinate system. I think it should be easy to understand. I want to draw a circle on the screen x == 500,y == 500, radius ==150, so it looks like this:

Isn't it clear? You can see where the View is at a glance.

## Canvas

### Canvas base usage

#### Draw background color

``````//color: use 0x hexadecimal color values
void drawColor(int color);
// Allow a, R,g,b to be passed in. Each color value ranges from 0 to 255
void drawARGB(int a, int r, int g, int b)
// Only R.G.B color components are allowed to be passed in. Transparency alpha is 255
void drawRGB(int r, int g, int b)
Copy the code``````

Draw a red background that looks like this

``````    override fun draw(canvas: Canvas) {
super.draw(canvas)
/ / 1.
canvas.drawColor(Color.RED)
/ / 2.
/ / canvas. DrawARGB (0 XFF, 0 XFF, 0, 0 x00)
/ / 3.
/ / canvas. DrawRGB (0 XFF, 0 x00 to 0 x00)
}
Copy the code``````

All three apis have the same effect

#### Draw a straight line

``````//startX,y
//stopX,y endpoint
/ / paint brush
void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint)
void drawLines(@Size(multiple = 4) @NonNull float[] pts, int offset, int count,
@NonNull Paint paint)
void drawLines(@Size(multiple = 4) @NonNull float[] pts, @NonNull Paint paint)
Copy the code``````
``````    override fun draw(canvas: Canvas) {
super.draw(canvas)
mPaint.strokeWidth = 10f
mPaint.setColor(Color.RED)
/ / 1.
canvas.drawLine(100f,100f,600f,600f,mPaint)
/ / 2.
canvas.drawLines(floatArrayOf(
100f,100f,600f,600f
),mPaint)
// The first argument is a set of coordinate points
// Which coordinate point the second argument starts from
// The third parameter coordinates point is to fetch 4 data
canvas.drawLines(floatArrayOf(
100f,100f,600f,600f
),0.4,mPaint)
}
Copy the code``````

The effect is the same

#### Plot points

``````/ / x, y coordinates
void drawPoint(float x, float y, @NonNull Paint paint)
// PTS coordinate group, offset from which point, count: how many points
void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
@NonNull Paint paint)
/ / PTS coordinate groups
void drawPoints(@Size(multiple = 2) @NonNull float[] pts, @NonNull Paint paint)
Copy the code``````
``````        / / 1
canvas.drawPoint(100f, 100f, mPaint)

/ / 2.
var offset = 0
var pts = floatArrayOf(
500f + offset, 100f, 500f + offset, 200f,
500f + offset, 300f, 500f + offset, 600f,
500f + offset, 700f, 500f + offset, 800f,
500f + offset, 900f, 500f + offset, 1000f
)
mPaint.setColor(Color.BLUE)
canvas.drawPoints(pts, 0.16, mPaint)

/ / 3.
mPaint.setColor(Color.GREEN)
offset = 100
pts = floatArrayOf(
500f + offset, 100f, 500f + offset, 200f,
500f + offset, 300f, 500f + offset, 600f,
500f + offset, 700f, 500f + offset, 800f,
500f + offset, 900f, 500f + offset, 1000f
)
canvas.drawPoints(pts, mPaint)
Copy the code``````

The effect is as follows:

#### Draw a rectangle

RectF: Saves a float rectangle structure

Rect: Saves the int rectangle structure

Top,right, and bottom (left,top,right,bottom, left,top, bottom, left,top,right,bottom

``````/ / 1.
void drawRect(@NonNull RectF rect, @NonNull Paint paint) {
super.drawRect(rect, paint);
/ / 2.
void drawRect(@NonNull Rect r, @NonNull Paint paint)
/ / 3.
void drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)

Copy the code``````
``````        / / 1.
var rect = RectF(100.50f,100.50f,500.50f,500.50f)
mPaint.style = Paint.Style.FILL
canvas.drawRect(rect,mPaint)

/ / 2.

var rect2 = Rect(300.300.600.600)
mPaint.style = Paint.Style.FILL
mPaint.setColor(Color.BLUE)
mPaint.alpha = 100
canvas.drawRect(rect2,mPaint)

/ / 3.
mPaint.style = Paint.Style.FILL
mPaint.setColor(Color.YELLOW)
canvas.drawRect(500f,500f,1000f,1000f,mPaint)
Copy the code``````

The effect is as follows:

#### Draw the path

``````// Draw a path according to path
void drawPath(@NonNull Path path, @NonNull Paint paint)
Copy the code``````

``````        /** * 1. Draw a path line */
var path = Path()
//1. Set the starting point
path.moveTo(100f, 100f)
//2. The starting point of the second line is the start of the moveTo setting
path.lineTo(100f,300f)
//3. The beginning of the third line is the end of the second, and so on
path.lineTo(300f,500f)
path.lineTo(500f,200f)
4 / / closed
path.close()
canvas.drawPath(path, mPaint)

/** * 2. Draw a radian path */
var path2 = Path()
// Draw the starting position of radians
path2.moveTo(100f,600f)
var rectF = RectF(100f,600f,600f,1000f)
// The first argument generates the rectangle of the ellipse, the second argument is the Angle at which the arc begins 0 degrees in the positive X-axis, and the third argument is the Angle at which the arc continues
path2.arcTo(rectF,0f,90f)
canvas.drawPath(path2, mPaint)
Copy the code``````

The above notes are very detailed, not to explain

#### Draw a circle/ellipse

``````// Draw the ellipse
void drawOval(@NonNull RectF oval, @NonNull Paint paint)
/ / draw circle
void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
Copy the code``````

``````        /** * 1. Draw ellipse */
canvas.drawOval(RectF(100f,500f,600f,800f),mPaint)

/** * 2. Draw circle */
mPaint.setColor(Color.YELLOW)
mPaint.alpha = 100
canvas.drawCircle(400f,400f,200f,mPaint)
Copy the code``````

#### Draw the Bitmap

``````//
val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.gild_3)
// The second and third parameters represent the starting position
canvas.drawBitmap(bitmap,100f.100f,mPaint)
Copy the code``````

#### Draw the Text

``````/ / 1.
void drawText(@NonNull char[] text, int index, int count, float x, float y,
@NonNull Paint paint)
/ / 2.
void drawText(@NonNull String text, float x, float y, @NonNull Paint paint)
/ / 3.
void drawText(@NonNull String text, int start, int end, float x, float y,
@NonNull Paint paint)
/ / 4.
void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
@NonNull Paint paint)
Copy the code``````

``````        /** * 1. * Use positions 0 to 5 to draw */
mPaint.textSize = 100f
canvas.drawText(charArrayOf('1'.'2'.'3'.'4'.'5'),0.5.200f,200f,mPaint)

/ * * * 2. * /
canvas.drawText("12345".300f,300f,mPaint)

/** * 3. Use positions 0 to 5 to draw */
canvas.drawText("12345".0.5.400f,400f,mPaint)
Copy the code``````

#### Draws Text based on the path

``````// Draw text by offsetting vOffset PX from hOffset
void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
float vOffset, @NonNull Paint paint)
Copy the code``````

``````        mPaint.setColor(Color.GREEN)
mPaint.alpha = 100

mPaint.textSize = 100f
var path = Path()
//1. Set the starting point
path.moveTo(300f, 300f)
//2. The starting point of the second line is the start of the moveTo setting
path.lineTo(300f,500f)
//3. The beginning of the third line is the end of the second, and so on
path.lineTo(500f,800f)
path.lineTo(800f,200f)
4 / / closed
path.close()
canvas.drawPath(path,mPaint)
// Shift the pixel from 0 to 100px
canvas.drawTextOnPath("12345asodnaspdnfpoashfeuinfapjn",path,0f,100f,mPaint)
Copy the code``````

#### Draw arcs/sectors

``````/ / 1.
void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,
@NonNull Paint paint)
/ / 2.
void drawArc(float left, float top, float right, float bottom, float startAngle,
float sweepAngle, boolean useCenter, @NonNull Paint paint)
Copy the code``````

``````        var rectF = RectF(100f, 100f, 500f, 500f)
/** * 1. Draw arc *@paramOvar: rectangular coordinate *@paramStartAngle: startAngle *@paramSweepAngle: End Angle *@paramUserCenter: If true, includes the center of the ellipse in the arc *@paramPaint: paint */
canvas.drawArc(rectF, 0f, 90f, true, mPaint)

/** * 2. Draw arc */
canvas.drawArc(100f,500f,500f,900f,0f,90f,false,mPaint)
Copy the code``````

#### Draw rounded rectangles

``````/ / 1.
void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint)
/ / 2.
void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
@NonNull Paint paint)
Copy the code``````

``````        /** * 1. Draw a rounded rectangle according to RectF *@paramRx: the radius of a fillet on the X-axis *@paramRy: Radius of fillet on y axis */
canvas.drawRoundRect(rectF,50f,50f,mPaint)
/** * 2. Draw a rounded rectangle */ based on the input rectangle position
canvas.drawRoundRect(100f,600f,500f,900f,100f,100f,mPaint)
Copy the code``````

#### Canvas transform

##### Translate - Layer translation
``````//dx/dy:x/y point new position
void translate(float dx, float dy)
Copy the code``````

##### Scale - Layer scale down 0 to 1
``````// The larger the reduction coefficient of x and y is between 0 and 1, the closer it is to the original image
void scale(float sx, float sy)
Copy the code``````

``````        /** * 1. Original rectangle */
mPaint.color = Color.RED
mPaint.alpha = 100
canvas.drawRoundRect(rectF,50f,50f,mPaint)

/** * 2. Shrink the original image by 0.5 times */
var rectF2 = RectF(100f, 100f, 500f, 500f)
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.scale(0.5f,0.5f)
canvas.drawRoundRect(rectF2,50f,50f,mPaint)
Copy the code``````
##### Rotate - Layer rotation
``````/ / 1.
void rotate(float degrees)
/ / 2.
void rotate(float degrees, float px, float py)
Copy the code``````

``````        /** * 1. Original rectangle */
mPaint.color = Color.RED
mPaint.alpha = 100
canvas.drawRoundRect(rectF,50f.50f,mPaint)

/** * 2. Rotate the original shape 45° */
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.rotate(45f)
canvas.drawRoundRect(rectF,50f.50f,mPaint)

/** * 3. Rotate the original figure 280° * at the coordinates point 500,100 clockwise 280° */
mPaint.color = Color.YELLOW
mPaint.alpha = 100
canvas.rotate(280f.500f.100f)
canvas.drawRoundRect(rectF,50f.50f,mPaint)
Copy the code``````
##### Skew - Layers cut incorrectly
``````// Tangent is a plane graph obtained by scaling the directed distance between each point of the graph and a line parallel to that direction in a certain direction. Horizontal tangent (or tangent parallel to the X-axis) is an operation that maps any point (X,y) to a point (X +my,y), where m is a fixed parameter called the tangent factor
// Sx and SY are the tangent factors, which are the tan value of the tilt Angle, where the tilt Angle of 45 degrees is 1
void skew (float sx, float sy)
Copy the code``````

``````        /** * 1
mPaint.color = Color.RED
mPaint.alpha = 100
canvas.drawRoundRect(rectF,50f,50f,mPaint)
/** * 2. The layer starts cutting incorrectly */
canvas.skew(0f,0.5f)
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.drawRoundRect(rectF,50f,50f,mPaint)
Copy the code``````
##### Matrix

API details

``````        /** * original graphics */
canvas.drawBitmap(mBitmap,100f,100f,mPaint)
/** *1. Shift the matrix 500,500 */
var matrix = Matrix()
matrix.setTranslate(500f,500f)
canvas.drawBitmap(mBitmap,matrix,mPaint)

/** * 2. The matrix is scaled 0.5 times */
var matrix2 = Matrix()
matrix2.setScale(0.5f,0.5f)
canvas.drawBitmap(mBitmap,matrix2,mPaint)

/** * 3. Matrix rotation 125° */
var matrix3 = Matrix()
matrix3.setRotate(125f,500f,500f)
canvas.drawBitmap(mBitmap,matrix3,mPaint)

/** * 4
var matrix4 = Matrix()
matrix4.setSkew(0.5f,0.5f)
canvas.drawBitmap(mBitmap,matrix4,mPaint)
Copy the code``````

#### Cut out the canvas

``````/ / cutting
boolean clipRect(RectF rect, Region.Op op);
boolean clipRect(Rect rect, Region.Op op);
boolean clipRect(RectF rect);
boolean clipRect(Rect rect);
boolean clipRect(float left, float top, float right, float bottom, Region.Op op);
boolean clipRect(float left, float top, float right, float bottom);
boolean clipRect(int left, int top, int right, int bottom);
boolean clipPath(Path path, Region.Op op);
boolean clipPath(Path path);
boolean clipRegion(Region region, Region.Op op);
boolean clipRegion(Region region);
Copy the code``````

The 1 in the figure above represents the original layer, uncropped; 2 represents the clipped layer; 3. No matter how you draw it, you can only draw it inside the region.

``````        /** * 1
mPaint.color = Color.RED
mPaint.alpha = 100
canvas.drawRect(300f.300f.700f.700f,mPaint)
canvas.drawText("1. The original".400f.600f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 100f
it.color = Color.WHITE
})
/** * 2. Cut a canvas in the RectF rectangle area
var rectf2 = RectF(100f.100f , 500f.500f);
canvas.clipRect(rectf2)
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.drawColor(mPaint.color)
canvas.drawText("2.clip".200f.200f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 100f
it.color = Color.WHITE
})

/** * 3. Draw a rectangle */ at coordinates 700,700
mPaint.color = Color.YELLOW
mPaint.alpha = 100
canvas.drawRect(300f.300f.700f.700f,mPaint)
canvas.drawText("3. After cutting".350f.400f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 30f
it.color = Color.WHITE
})
Copy the code``````

#### Preservation and restoration of canvas

Save and restore usually appear in pairs. Save can save the current state of canvas, and then carry out a series of operations to change canvas, such as translation and clipping. Finally, restore is used to restore the canvas to the state at the time of save.

``````int save(a)      // Each time this function is called, the current canvas state is saved and placed on a specific stack
void restore(a)  // Restore the canvas by removing the state from the top of the stack
Copy the code``````

Save the layer by calling Canvas. save when it is not clipped, and restore the previous layer by calling Canvas. restore after clipping

``````        /** * 1
mPaint.color = Color.RED
mPaint.alpha = 100
canvas.drawRect(300f,300f,700f,700f,mPaint)
canvas.drawText("1. The original".400f,600f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 100f
it.color = Color.WHITE
})
/** * 2. Cut a canvas in the RectF rectangle area
var rectf2 = RectF(100f, 100f , 500f, 500f);
// Save the uncropped layers first
canvas.save()
canvas.clipRect(rectf2)
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.drawColor(mPaint.color)
canvas.drawText("2.clip".200f,200f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 100f
it.color = Color.WHITE
})

/** * 3. Draw a rectangle */ at coordinates 700,700
// Unstack after clipping
canvas.restore()
mPaint.color = Color.YELLOW
mPaint.alpha = 100
canvas.drawRect(300f,300f,600f,600f,mPaint)
canvas.drawText("3. After cutting".350f,400f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 30f
it.color = Color.WHITE
})
Copy the code``````

#### Canvas and layer

The previous section described how save can be used to save layers. Here are some apis that can also be used to save layers

``````// Bounds: The holding object corresponding to the area to save
//saveFlags: ALL_SAVE_FLAG indicates that all information is saved
public int saveLayer(RectF bounds,Paint paint,int saveFlags)
public int saveLayer(float left,float top,float right,float bottom,Paint paint,int saveFlags)

Copy the code``````

Draw a clipped area in red and then comment 2 on Draw a circle viewable. You can only draw in the clipped area, but if you draw in the circle viewable area after calling Canvas. RestoreToCount, you will not be affected by comment 3 on the viewable area.

``````        /** * 1
mPaint.color = Color.RED
mPaint.alpha = 200
canvas.drawRect(300f,300f,1000f,1000f,mPaint)
canvas.drawText("1. Crop layers".750f,750f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 30f
it.color = Color.WHITE
})
/** * 2. Save */
val saveLayer = canvas.saveLayer(300f, 300f, 1000f, 1000f, mPaint,ALL_SAVE_FLAG)
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.drawCircle(500f,500f,300f,mPaint)
canvas.drawText("2. Draw circles".350f,700f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 30f
it.color = Color.WHITE
})

/** * 3. Restore layer */
canvas.restoreToCount(saveLayer)
/ * * * * /
mPaint.color = Color.BLUE
mPaint.alpha = 100
canvas.drawCircle(400f,400f,200f,mPaint)
canvas.drawText("3. Restore".350f,250f,Paint(Paint.ANTI_ALIAS_FLAG).also {
it.textSize = 30f
it.color = Color.WHITE
})
Copy the code``````

We have practiced all the commonly used Canvas apis here, and now we will proceed to the last part of this article. We will end the Canvas explanation with a sample demo.

### Canvas of actual combat

1. Draw simple clock

Let's take a look at the effect, as follows:

Code drawing structure:

``````    override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//1. Draw the outer circle
canvas.drawCircle(mX, mY, mR.toFloat(), mPaint!!)

//2. Draw the center
canvas.drawCircle(mX, mY, 15f, mPaintMinute!!)

//3. Draw the scale
drawLines(canvas)

//4. Draw the hour point
drawText(canvas)

//5. Update time
updateCurrentTime(canvas)
}
Copy the code``````

Here's how step 5 works:

``````/* * * get the current system time * * @param canvas canvas */

private fun updateCurrentTime(canvas: Canvas) {
// Get the current system time
val format = SimpleDateFormat("HH-mm-ss")
val time = format.format(Date(System.currentTimeMillis()))
val split = time.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hour = Integer.parseInt(split)
val minute = Integer.parseInt(split)
val second = Integer.parseInt(split)
// The Angle of the hour hand
val hourAngle = hour * 30 + minute / 2
// The Angle of the minute hand
val minuteAngle = minute * 6 + second / 10
// The Angle of the second hand
val secondAngle = second * 6

// Draw the clock with 12 o 'clock as the 0° reference point
drawLine(canvas, hourAngle, 170, mPaintHour)

// Draw minutes
drawLine(canvas, minuteAngle, 60, mPaintMinute)

// Draw seconds
canvas.rotate(secondAngle.toFloat(), mX, mY)
canvas.drawLine(mX, mY, mX, mY - mR + 80, mPaintSecond!!)

// Refresh the interface every 1s
postInvalidateDelayed(1000)}fun drawLine(canvas: Canvas, angle: Int, length: Int, mPaint: Paint?). {
canvas.rotate(angle.toFloat(), mX, mY)
canvas.drawLine(mX, mY, mX, mY - mR + length, mPaint!!)
canvas.save()
canvas.restore()
// Now that the clock is drawn, we need to turn the canvas back again and continue with 12 o 'clock as the 0° reference point
canvas.rotate((-angle).toFloat(), mX, mY)
}
Copy the code``````

It's actually quite simple. For details, see clockView.kt

2. Slide to switch color pictures

Let's take a look at the final effect first, and then we'll talk about how it works. Please take a look at the screen below:

Through this exercise you will learn:

1. A custom Drawable
2. Canvas Layer processing

Here is the principle to achieve the above effect:

1. cutout
2. Mosaic
3. With the need to cut w according to the slide, H dynamic splicing

Let's start with an example of custom Drawable code

``````public class CustomDrawable : Drawable {

lateinit var unseleter: Drawable
lateinit var selecter: Drawable

constructor(unseleter: Drawable, selecter: Drawable) : super() {
this.selecter = selecter
this.unseleter = unseleter

}

override fun draw(canvas: Canvas) {
// Get the current rectangle of its Drawable
val bounds = bounds
//1. Draw the gray part
drawGrayDraw(bounds,canvas)
//2. Draw the color part
drawColorDraw(bounds,canvas)
}

/** * Draw the gray area *@linkGravity can refer to https://www.cnblogs.com/over140/archive/2011/12/14/2287179.html * / you do not understand
private fun drawGrayDraw(bound: Rect, canvas: Canvas) {
val rect = Rect()
Gravity.apply(
Gravity.LEFT,// Start to matting from the left or right
bound.width(),  // The width of the target rectangle
bound.height(), // The height of the target rectangle
bound,// The rect is pulled out
rect The rect / / target
)
canvas.save() // Save the current canvas
canvas.clipRect(rect)
unseleter.draw(canvas)
canvas.restore()

}

/** * draw the colored area */
private fun drawColorDraw(bounds: Rect, canvas: Canvas) {
val rect = Rect()
Gravity.apply(
Gravity.RIGHT,// Start to matting from the left or right
bounds.width()/3.// The width of the target rectangle
bounds.height(), // The height of the target rectangle
bounds,// The rect is pulled out
rect The rect / / target
)
canvas.save() // Save the current canvas
canvas.clipRect(rect)
selecter.draw(canvas)
canvas.restore()

}

override fun setAlpha(alpha: Int){}override fun getOpacity(a): Int  = 0

override fun setColorFilter(colorFilter: ColorFilter?).{}override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
// Reassign after the change
unseleter.bounds = bounds
selecter.bounds = bounds
}

override fun getIntrinsicHeight(a): Int {
return Math.max(selecter.intrinsicHeight, unseleter.intrinsicHeight)
}

override fun getIntrinsicWidth(a): Int {
return Math.max(selecter.intrinsicWidth, unseleter.intrinsicWidth)
}

override fun onLevelChange(level: Int): Boolean {
// If the level changes, remind yourself to redraw
invalidateSelf()
return super.onLevelChange(level)

}

}
Copy the code``````

The running effect is as follows:

This is a static matting + splicing, if we dynamically slide and then calculate the clipping width and height is realized according to the slide to display the corresponding color picture. The demo code.

## conclusion

Canvas based API mapping and real example, here is already all over, these apis in fact need not back, AS long AS you know what you need to draw, and then according to the AS automatic prompt, take a look at what then need to fill in the parameters on, parameter meaning is not clear, baidu, don't draw a circle, a rectangle is baidu, You can't remember it at all. You must practice more. See you next time!

Thank you for reading, I hope to bring you help!

Search
Categories