Android Custom View series
- (1) To achieve the effect of rounded corner mask
- Android custom View Demo (2) to achieve the circular avatar effect
- Android custom View Demo (3) to achieve the micro channel shot one shot of the animation effect
Android custom View to achieve a circular avatar effect
In our APP, we often encounter the need to show the circular profile picture, usually through Glide can achieve, but let’s make a circular profile picture, if let us customize to achieve this effect, how to do?
Ok, this article will do this in three ways!
Note: This is a hands-on Demo
1. Knowledge points to be learned from this article
- Use of canvas. ClipPath API
- The use of Xfermode
- Paint’s Xfermode and ShaderAPI
- Matrix translation and Canvans translation (in the source code, in order to display all three effects simultaneously in one View, the Canvas coordinates are translated)
- Summarize the advantages and disadvantages of the three implementations
2. Create a circular avatar through a custom View
There are three ways to do this, and what are the three ways?
- Through the canvas. ClipPath ()
- Through the paint.xfermode = porterDuffXfermode
- Through the paint.shader = bitmapShader
3. The first implementation
Use the Canvas clip method
private val AVATAR_SIZE = 240.dp // The size of the picture
private val RADIUS = AVATAR_SIZE / 2 // Cut the radius of the circle
class CircleAvatarView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint()
private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())// The bitmap of the image
private val circlePath = Path()// The circular path
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
/** * Draw a circle */ where the center of the circle is x: half of the width of the View y: half of the height of the View and the radius is half of the image size
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// The KTX extension method will automatically save the recovery Canvas
canvas.withSave {
// Reset the brush
paint.reset()
// Use the Canvas to cut the range to be drawn, namely the circle
canvas.clipPath(circlePath)
// Draw the declared Bitmap after the crop
canvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)
}
}
}
Copy the code
Explain the code and, of course, look at the code comments as well
- Define the width of the package-level image to 240dp and the radius of the circle
- Gets a Bitmap of the image to display
- Declares the Path of a circle to crop
- Add a circle to the onSizeChanged method
- Use the Canvas to cut the range to be drawn, that is, the circle
- Draw the declared Bitmap after the crop
The above is the first method of drawing the circular head, mainly using the canvas.clippath (circlePath) method, pay attention to the canvas save and restore
4. Second way of implementation
Through the paint.xfermode = porterDuffXfermode
PorterDuff.Mode
private val AVATAR_SIZE = 240.dp // The size of the picture
private val RADIUS = AVATAR_SIZE / 2 // Cut the radius of the circle
class CircleAvatarView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint()
private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())// The bitmap of the image
private val circlePath = Path()// The circular path
private val bounds = RectF()// Off-screen buffered bounds
private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
/** * Draw a circle */ where the center of the circle is x: half of the width of the View y: half of the height of the View and the radius is half of the image size
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
/** * Set the off-screen buffered bounds to be small enough to affect performance */
bounds.set(width / 2f - RADIUS, height / 2f - RADIUS,
width / 2f + RADIUS, height / 2f + RADIUS)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Enable off-screen buffering
val count = canvas.saveLayer(bounds, null)
paint.reset()
canvas.drawPath(circlePath, paint)
// Set paint's xferMode to porterduff.mode.src_in
paint.xfermode = porterDuffXfermode
// Draw the Bitmap in the current Paint
canvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)
// Clear paint's xfermode
paint.xfermode = null
// Draw the off-screen cached content to the View
canvas.restoreToCount(count)
}
}
Copy the code
Explain the code and, of course, look at the code comments as well
- Set the extent of the off-screen buffer in the onSizeChanged method, which is the extent of the outer rectangle of the circular avatar
- Turn on off-screen buffering in the onDraw method
- Canvas first draws a circle (equivalent to Destination image in Porterduff.mode)
- Set the xferMode of paint to porterduff.mode.src_in
- Draw the Bitmap in the current Paint (note that this is the same as the Source image in porterduff.mode). Since we chose porterduff.mode.src_in, we draw the image we want
- I’m going to draw the off-screen buffer to the View
Note: Be sure to turn on off-screen buffering, otherwise the result may not be what you expect. Off-screen buffering is equivalent to taking a transparent View and drawing the graph that the Canvas is drawing
5. Third way of implementation
Through the paint.shader = bitmapShader
private val AVATAR_SIZE = 240.dp // The size of the picture
private val RADIUS = AVATAR_SIZE / 2 // Cut the radius of the circle
class CircleAvatarView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint()
private var avatar = getAvatar(R.drawable.my_avatar, AVATAR_SIZE.toInt())// The bitmap of the image
private val circlePath = Path()// The circular path
private val bitmapShader = BitmapShader(avatar, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
/** * Draw a circle */ where the center of the circle is x: half of the width of the View y: half of the height of the View and the radius is half of the image size
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.reset()
// Set Shader for paint
paint.shader = bitmapShader
// Draw the circle as a Shader
canvas.drawPath(circlePath, paint)
}
}
Copy the code
Explain the code and, of course, look at the code comments as well
This way, it’s easier
- Declare a BitmapShader. Fill the bitmap of the avatar in the BitmapShader construct and fill in the value of the shader. TileMode parameter
- Set the Shader for the paint in the onDraw method
- Draw a circle in the form of a Shader and the result is a head with rounded corners
6. Summary
Summarize the advantages and disadvantages of the three approaches
1. Canvas. ClipPath () :
The canvas clip method has no anti-aliasing effect and will have burrs because it is precisely cut pixelsCopy the code
2.paint.xfermode = porterDuffXfermode
The xfermode method using Paint has anti-aliasing effect, no burrs, good effect, anti-aliasing treatment, fill and similar translucent color around etcCopy the code
3.paint.shader = bitmapShader
Shader with paint has anti-aliasing effect, no burrs, good effect but the image results are affected by shader. TileMode, which may not be what you expectCopy the code
To sum up: it’s better to use paint. Xfermode = porterDuffXfermode, because what we need is a perfectly displayed avatar
Also be careful to use off-screen buffering
7. Source address
Note: my source code is to draw the three ways in a View, and through the Matrix translation or Canvans translation to achieve the effect of the downward arrangement, the way to practice the Matrix translation and Canvans translation
CircleAvatarView.kt
8. The original address
Android custom View to achieve a circular avatar effect
9. Refer to articles
hencoder
PorterDuff.Mode
I recommend my open source project WanAndroid client
WanAndroidJetpack architecture diagram
- A pure Android learning project, WanAndroid client.
- Project use
MVVM
Architecture,Kotlin
Voice writing. - Extensive use of Android Jetpack includes, but is not limited to
Lifecycle
,LiveData
,ViewModel
,Databinding
,Room
,ConstraintLayout
And more in the future. - using
Retrofit
和Kotlin-Coroutine
coroutinesNetwork interaction. - Loading pictures
Glide
Mainstream load picture frame. - Data storage is mainly used
Room
And tencentMMKV
.
Kotlin + MVVM + Jetpack + Retrofit + Glide is a good project to learn MMVM architecture.
This project itself is also a special learning Android related knowledge of the APP, welcome to download the experience!
Source code address (with download link)
WanAndroidJetpack
Overview of the APP
Order if you likeStarsIf you have questions, please refer to the following questions: