本文使用两种方式,实现Compose中图片的异步加载

前言

Android开发中异步加载图片是非常常见的需求。本文将带你实现这一需求。
本文将分为如下两个方面:

  1. 自己写函数
  2. 用开源库

实现

借助Glide库自己写

Glide开源库基本上成为了Android中加载图片的首选,其简单易用的API和强大的缓存能力让这一过程变得十分方便。
自然在Jetpack Compose中也可以使用。

引入依赖

在模块中的build.gradle中加入

1
2
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'

编写函数

如何让Glide把图片加载到Compose组件上去呢?我们可以利用其提供的into(Target)指定自定义的target,再搭配上mutableState<Bitmap>的返回值,即可实现在图片加载完成后Compose自动更新
图片加载时一般会有一个默认的loading图,我们可以如法炮制,让Glide先帮我们加载一张本地图片,然后再去加载网络图片即可。
编写的函数如下:

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
/**
* 使用Glide库加载网络图片
* @author [FunnySaltyFish](https://funnysaltyfish.github.io)
* @date 2021-07-14
* @param context Context 合理的Context
* @param url String 加载的图片URL
* @param defaultImageId Int 默认加载的本地图片
* @return MutableState<Bitmap?> 加载完成(失败为null)的Bitmap-State
*/
fun loadImage(
context: Context,
url: String,
@DrawableRes defaultImageId: Int = R.drawable.load_holder
): MutableState<Bitmap?> {
val TAG = "LoadImage"
val bitmapState: MutableState<Bitmap?> = mutableStateOf(null)

//为请求加上 Headers ,提高访问成功率
val glideUrl = GlideUrl(url,LazyHeaders.Builder().addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.67").build())

//先加载本地图片
Glide.with(context)
.asBitmap()
.load(defaultImageId)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
//自定义Target,在加载完成后将图片资源传递给bitmapState
bitmapState.value = resource
}

override fun onLoadCleared(placeholder: Drawable?) {}
})

//然后再加载网络图片
try {
Glide.with(context)
.asBitmap()
.load(glideUrl)
.into(object : CustomTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
//自定义Target,在加载完成后将图片资源传递给bitmapState
bitmapState.value = resource
}

override fun onLoadCleared(placeholder: Drawable?) {}
})
} catch (glideException: GlideException) {
Log.d(TAG, "loadImage: ${glideException.rootCauses}")
}

return bitmapState
}

使用例子

简单的例子如下:

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
@Composable
fun LoadPicture(
url : String
){

val imageState = loadImage(
context = LocalContext.current,
url = url,
)

Card(modifier = Modifier
.padding(4.dp)
.clickable { }) {
//如果图片加载成功
imageState.value?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = "",
modifier = Modifier
.padding(4.dp)
.fillMaxWidth()
)
}
}
}

//......

LazyColumn {
val urls = arrayListOf<String>()
for (i in 500..550){urls.add("https://nyc3.digitaloceanspaces.com/food2fork/food2fork-static/featured_images/$i/featured_image.png")}
itemsIndexed(urls){ _ , url -> LoadPicture(url = url)}
}

效果如下图所示:
加载中

加载中
加载完毕
加载成功
P.S.:别忘了声明网络权限哦!

借助开源框架

事实上,谷歌在其开发文档中也给出了示例,用的是开源库Accompanist
谷歌的官方例子
所以我们也可以用这个

简单的例子

1
implementation "com.google.accompanist:accompanist-coil:0.13.0"
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
/**
* @author [FunnySaltyFish](https://funnysaltyfish.github.io)
* @date 2021-07-14
* @param url 加载的链接
*/
@Composable
fun LoadPicture2(url:String){
val painter = rememberCoilPainter(url)

Box {
Image(
painter = painter,
contentDescription = "",
)

when (painter.loadState) {
is ImageLoadState.Loading -> {
// 显示一个加载中的进度条
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
is ImageLoadState.Error -> {
// 如果发生了什么错误,你可以在这里写
Text(text = "发生错误", color = MaterialColors.RedA200)
}
else -> Text(text = "未知情况", color = MaterialColors.PurpleA400)
}
}
}

效果如下:
在这里插入图片描述

加载中

P.S.:如果想更好的看到加载的情况,可以在模拟器设置中将网络类型设置为较慢的类型

一些限制

个人感觉,这种方式有以下的问题:

  • 滑动加载时不如Glide流畅
  • 对Kotlin版本有要求,如(截止文章写作时)最新的0.13.0版就必须用kotlin1.5及以上版本,否则就会编译出错

参考