前段时间在看 ActivityPub 文档时发现它们的客户端在加载图片时使用了一种叫做 BlurHash 的技术来显示图片加载出来之前的 Placeholder,效果非常好,而且成本极低,如果集成到 App 中可以很大程度上提高多图页面的用户体验。
这种技术可以让你图片加载出来之前显示一个填充图,这个图看起来有点像原图的高斯模糊,并且显示这个“高斯模糊”时并不需要提前把图片加载出来,计算成本也很低,完全可以当作图片加载完成前的 Placeholder 使用,而不必使用纯色块来填充,类似于下图这样:
右边就是 BlurHash 生成的图片,看起来效果还是可以的,下面就说要如何实现这种技术。
BlurHash 的使用
上面图中的中间图片有一个称为 BlurHash 的字符串,这个字符串就是通过算法计算出来的左边的原图的颜色分布信息,计算结果是一个不算长的字符串,然后客户端按照算法输入 BlurHash String,即可生成右边的那种看起来像高斯模糊一样的图片。
BlurHash 本质上就是一系列算法,因此适用所有的前端平台,包括 Web、Android、iOS 等等,接入的成本也很低。
使用起来是非常简单的,因为算法都很简单,不过我们需要后端的支持,因为后端接口在返回图片 url 给客户端时需要同时把这个图片的 BlurHash String 也返回过来,客户端根据 BlurHash 生成图片当作 Placeholder,再去加载图片,提供用户体验。
项目地址如下:
https://github.com/woltapp/blurhash
因为算法确实比较简单,他们甚至没有提供 SDK,只是提供了每一种语言的实现代码,大家直接把这个代码复制到项目中即可,Kotlin 代码就一个文件,提供 BlurHash ,返回 Bitmap,就这么简单。
不过 Compose 实际使用时需要自己写一点胶水代码,也可以直接用我写好的。
fun Modifier.blurhash(blurHash: String): Modifier = composed {
var size: Size? by remember {
mutableStateOf(null)
}
var bitmap: Bitmap? by remember {
mutableStateOf(null)
}
val coroutineScope = rememberCoroutineScope()
if (size != null) {
DisposableEffect(blurHash) {
if (bitmap == null && size != null) {
coroutineScope.launch {
bitmap = withContext(Dispatchers.IO) {
BlurHashDecoder.decode(
blurHash = blurHash,
width = (size!!.width * 0.5F).roundToInt(),
height = (size!!.height * 0.5F).roundToInt(),
)
}
}
}
onDispose {
bitmap?.recycle()
bitmap = null
}
}
}
onSizeChanged {
size = it.toSize()
}.drawBehind {
if (bitmap != null && size != null) {
drawImage(
image = bitmap!!.asImageBitmap(),
dstSize = IntSize(size!!.width.roundToInt(), size!!.height.roundToInt()),
)
}
}
}
其他平台,iOS、Web 这些也按照类似的方式直接集成即可,后端的 SDK 也是有的,直接集成就行了。