Android 自定义View练手Demo(一)实现圆角遮罩效果
Android 自定义View练手Demo(二)实现圆形头像效果
Android 自定义View练手Demo(三)实现微信拍一拍的动画效果
Android自定义View实现圆形头像效果
在我们的APP中通常会遇到,展示圆形头像的需求,一般通过Glide就能实现,但是让我们做一个圆形头像,如果让我们自定义实现这种效果,该怎样做呢?
好,接下来本文通过
三种方式
来实现这种效果!
注意:这是一个练手的Demo
1.通过本文可以学到的知识点
canvas.clipPath API的使用
Xfermode的使用
Paint的Xfermode和ShaderAPI
Matrix的平移和Canvans的平移(源码中,为了在一个View同时展示三种效果,所以对Canvas坐标进行了平移)
总结三种实现方式的优缺点
2.通过自定义View制作圆形头像
通过三种方式来实现这种效果,是哪三种方式呢?
通过canvas.clipPath()
通过paint.
xfermode
= porterDuffXfermode
通过paint.
shader
= bitmapShader
3.第一种实现方式
利用 canvas的 clip方法
private val AVATAR_SIZE = 240.dp
private val RADIUS = AVATAR_SIZE / 2
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())
private val circlePath = Path()
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
* 在圆心 x:View宽度的一半 y:View高度的一半 半径为图片尺寸的一半 的位置上画圆
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.withSave {
paint.reset()
canvas.clipPath(circlePath)
canvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)
解释一下代码,当然看代码的注释也是一样的
定义包级别的图片的宽度为240dp和圆形的半径
获取一个要展示的图片的Bitmap
声明一个要裁剪的圆形的Path
onSizeChanged方法中添加一个圆形
利用Canvas来裁切去要画的范围,也就是那个圆形
在裁切后画上声明的Bitmap
以上就是绘制圆形头像的第一种方法,主要使用的是canvas.clipPath(circlePath)
方法,注意Canvas的保存和恢复
4.第二种实现方式
通过paint.xfermode = porterDuffXfermode
PorterDuff.Mode
private val AVATAR_SIZE = 240.dp
private val RADIUS = AVATAR_SIZE / 2
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())
private val circlePath = Path()
private val bounds = RectF()
private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
* 在圆心 x:View宽度的一半 y:View高度的一半 半径为图片尺寸的一半 的位置上画圆
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
* 设置离屏缓冲的 bounds最好不要太大,影响性能
bounds.set(width / 2f - RADIUS, height / 2f - RADIUS,
width / 2f + RADIUS, height / 2f + RADIUS)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val count = canvas.saveLayer(bounds, null)
paint.reset()
canvas.drawPath(circlePath, paint)
paint.xfermode = porterDuffXfermode
canvas.drawBitmap(avatar, width / 2 - RADIUS, height / 2 - RADIUS, paint)
paint.xfermode = null
canvas.restoreToCount(count)
解释一下代码,当然看代码的注释也是一样的
在onSizeChanged方法中设置离屏缓冲的范围,是圆形头像的外切矩形的范围
onDraw方法中开启离屏缓冲
Canvas先画了一个圆形(相当于PorterDuff.Mode中的Destination image)
设置paint的 xfermode 为PorterDuff.Mode.SRC_IN
以当前的Paint来画Bitmap(注意此时相当于PorterDuff.Mode中的Source image),因为我们选择的模式为PorterDuff.Mode.SRC_IN所以就画出我们想要的效果
把离屏缓冲的内容,绘制到View上去
注意:一定要开启离屏缓冲,不然结果可能不是你所预期的,离屏缓冲相当于拿出一块透明的View来绘制,Canvas要绘制的图形
5.第三种实现方式
通过paint.shader = bitmapShader
private val AVATAR_SIZE = 240.dp
private val RADIUS = AVATAR_SIZE / 2
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())
private val circlePath = Path()
private val bitmapShader = BitmapShader(avatar, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
* 在圆心 x:View宽度的一半 y:View高度的一半 半径为图片尺寸的一半 的位置上画圆
circlePath.addCircle(width / 2f, height / 2f, RADIUS, Path.Direction.CW)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.reset()
paint.shader = bitmapShader
canvas.drawPath(circlePath, paint)
解释一下代码,当然看代码的注释也是一样的
这种方式就比较简单了
声明一个BitmapShader,把头像的bitmap填入BitmapShader的构造中,并填入参数Shader.TileMode的值,这个具体看这篇文章对Paint做了详细解释
在onDraw方法中给paint设置Shader
以Shader的形式来画圆形,结果就是一个圆角头像了
总结一下三种方式的优缺点
1.canvas.clipPath():
利用 canvas的 clip方法
没有抗锯齿效果,会有毛边,因为是精确的切像素点
2.paint.xfermode = porterDuffXfermode
利用 paint的 xfermode 方法
有抗锯齿效果,没有毛边,效果好,做了抗锯齿的处理,填充和周边类似的半透明色等
3.paint.shader = bitmapShader
利用 设置 paint 的 shader 方法
有抗锯齿效果,没有毛边,效果好
但是图片结果会受到Shader.TileMode影响,可能结果不是你所预期的
综上总结:最好使用paint.xfermode = porterDuffXfermode这种方式,因为我们需要的是一个显示完美的头像
此外还要注意使用离屏缓冲
7.源码地址
注意:我的源码是把三种方式统一画在了一个View中,并通过Matrix的平移或者Canvans的平移来实现向下排列的效果,顺便先练了Matrix的平移和Canvans的平移
CircleAvatarView.kt
8.原文地址
Android自定义View实现圆形头像效果
9.参考文章
hencoder
PorterDuff.Mode
推荐一下我开源的项目 WanAndroid 客户端
WanAndroidJetpack 架构图
一个纯 Android 学习项目,WanAndroid 客户端。
项目采用 MVVM
架构,用 Kotlin
语音编写。
Android Jetpack 的大量使用包括但不限于Lifecycle
、LiveData
、ViewModel
、Databinding
、Room
、ConstraintLayout
等,未来可能会更多。
采用 Retrofit
和 Kotlin-Coroutine
协程进行网络交互。
加载图片 Glide
主流加载图片框架。
数据存储主要用到了 Room
和腾讯的 MMKV
。
Kotlin + MVVM + Jetpack + Retrofit + Glide 的综合运用,是学习 MMVM 架构的不错的项目。
此项目本身也是一个专门学习 Android 相关知识的 APP,欢迎下载体验!
源码地址(附带下载链接)
WanAndroidJetpack
APP 整体概览
喜欢的点个 Stars,有问题的请提 Issues。