自定义头像装饰View实现

前言

经常玩B站的同学应该都知道,认证后的用户的头像右下角会有一个小闪电的图标,有的是黄色的、有的是蓝色的。我想到 阳光沙滩VIP 用户,我们也可以对用户群体进行区分,所以咱也整一个吧。

img

效果图

图片描述
图片描述
图片描述


思路分析

思路1

我们可以使用组合控件的方式来实现这个效果,具体结构如下图所示:

图片描述

  • 优点

实现起来比较简单。

  • 缺点

View 嵌套,且 View 个数较多。


思路2

除此之外,我们还可以通过自定义 ImageView 来实现。

首先 ImageView 的 setImageDrawable 方法设置头像图片,然后通过设置 ImageView 的 foreground 属性来实现添加右下角小图标的效果。

  • 优点

只有一个 View ,不存在 View 的嵌套,性能相对较高;

  • 缺点

实现较为复杂,需要具备自定义 View 的能力;


源码实现

代码比较简单,前面也讲了思路,关键点也在代码中加了注释。

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
93
94
95
96
97
98
99
100
101
102
103
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.content.ContextCompat
import androidx.core.graphics.scaleMatrix
import cn.cqautotest.sunnybeach.R
import cn.cqautotest.sunnybeach.http.glide.GlideApp
import cn.cqautotest.sunnybeach.util.dp
import com.blankj.utilcode.util.ImageUtils

/**
* author : A Lonely Cat
* github : https://github.com/anjiemo/SunnyBeach
* time : 2022/03/31
* desc : 头像装饰控件
*/
class AvatarDecorView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatImageView(context, attrs) {

// 默认头像 drawable
private val defAvatarDrawable = ContextCompat.getDrawable(context, R.mipmap.ic_default_avatar)

// 边框 drawable
private val decorDrawable = GradientDrawable().apply {
shape = GradientDrawable.OVAL
setStroke(1.dp, ContextCompat.getColor(context, R.color.pink))
}

// 前景徽章图标的画笔
private val badgePaint = Paint(Paint.ANTI_ALIAS_FLAG)

// 小徽章 bitmap
private val badgeBitmap = ImageUtils.getBitmap(R.drawable.ic_flash)

// 缩放因子
private val scaleFactor = 0.22f

// 缩放矩阵
private val scaleMatrix = scaleMatrix(scaleFactor, scaleFactor)

// 是否为 VIP
var isVip: Boolean = false

init {
// 如果 xml 里没有设置资源图片,则设置默认的头像 drawable
if (drawable == null) setImageDrawable(defAvatarDrawable)
}

/**
* 加载头像(剪裁成圆形)
* 参数:
* 1、是否为 VIP
* 2、资源
* 3、边框的自定义配置
*/
fun loadAvatar(vip: Boolean = false, resource: Any?, block: GradientDrawable.() -> Unit = {}) {
isVip = vip
block.invoke(decorDrawable)
GlideApp.with(this)
.load(resource)
.placeholder(R.mipmap.ic_default_avatar)
.error(R.mipmap.ic_default_avatar)
.circleCrop()
.into(this)
}

/**
* 使用 Glide 加载 ImageView 图片时会调用此方法,我们可以在这里对设置的 drawable 资源进行修饰。
*/
override fun setImageDrawable(drawable: Drawable?) {
val decorDrawable = if (isVip && drawable != null) {
// 如果是 VIP 且 drawable 资源不为空,则添加 decorDrawable
LayerDrawable(arrayOf(drawable, decorDrawable))
} else {
// 如果设置的头像 drawable 为空,则设置默认头像 drawable
drawable ?: defAvatarDrawable
}
super.setImageDrawable(decorDrawable)
}

/**
* 绘制前景徽章图标
*/
override fun onDrawForeground(canvas: Canvas?) {
super.onDrawForeground(canvas)
canvas ?: return
if (isVip) {
// Timber.d("onDrawForeground:===> width is $width measuredWidth is $measuredWidth")
val dx = width - badgeBitmap.width * scaleFactor
val dy = height - badgeBitmap.height * scaleFactor
// 画布默认是在左上角,需要先调整画布的位置
canvas.translate(dx, dy)
// 绘制小徽章
canvas.drawBitmap(badgeBitmap, scaleMatrix, badgePaint)
}
}
}

结语

我写的 阳光沙滩APP项目 中也有使用到此功能,感兴趣的同学可以 clone 下来看看。如果只是想体验一下APP的话,请点击下载APP体验 (密码:5qlt)。


自定义头像装饰View实现
https://blog.funkt.cn/2022/04/01/1510085424304943106/
作者
anjiemo
发布于
2022年4月2日
许可协议
未经作者授权,禁止转载