Jetpack Compose异步加载图像

做一名Android开发者是一件喜忧参半的事。

一方面,我们能创造出很多优质的app,设计出既连贯又清晰的基础代码让用户坐享我们的开发成果。

另一方面,如果你问经验丰富的Adroid 开发者关于开发Android的重点是什么,他们大多数都会给出一个相同的答案——用劣质的XML文件构建UI。

启动Jetpack Compose

Jetpack Compose是用Kotlin for Android来编写UI代码的一种新的声明式方法,针对Jetpack Compose有很多资源可供参考,我推荐这个由 Leland Richardson(Android工具包团队的主要工程师之一)提供的 视频,很多出色的代码实验室 也是按照此视频进行操作的。

接下来让我们一起创建图像吧!

Compose中的所有UI组件都是窗口小部件,并且这些小部件基本上都是使用 Composable 批注来进行批注的函数。

与Kotlin中的 suspend 关键字相似,Compose中的可组合函数只能从其他可组合函数中调用,有关更多信息,请参见 本视频

让我们看一下Compose中的一个基本小部件:

@Composable
 fun MyImage() {
Image(imageResource(id = R.drawable.ic_launcher)) }

如我们所见,这是一个显示带有可绘制资源图像的窗口小部件。

但是,等等……有可能我在app中所展示的大多数图像都是从网络加载的,这属于异步工作,在Android视图系统中我有大量的文件库来对其进行处理,但这如果放在Compose中我该怎么做呀?!

!1_EDziGp2Ryr0GxymMH8bkFw

Compose社区

幸运的是,Compose社区真的很棒。我在 Leland Richardson的#compose slack频道上看到了一个帖子,最后,我终于开窍了。

对于这个小部件我们使用 Picasso ,但是只要能为我们提供图像加载的 Target 接口,那么其他任何图像加载库都可以对其进行使用。

要想将Picasso合并到你的Android app中,请在 build.gradle 文件中添加以下依赖项:

dependencies { implementation 'com.squareup.picasso:picasso:2.71828' }

下面就可以开始构建自定义部件啦!

首先,我们像往常一样,创建一个以 Composable 批注进行批注的函数。

    @Composable
 fun NetworkImage(url: Sting) { 
Image(asset = <Where can we get an asset for the image?>) }

当使用图像加载器将URL加载成图像时,我们通常需要引用一个视图,且可以从中查看图像。但是在Compose中我们没有视图,那我们会把图像加载到哪里了呢?

Picasso和其他大多数图像加载器都具有Target接口,该接口提供了诸如onSuccess和onFail之类的回调。 Picasso中的界面如下所示:

public interface Target { 
     void onBitmapLoaded(Bitmap var1, LoadedFrom var2); 

     void  onBitmapFailed(Exception var1, Drawable var2);

     void onPrepareLoad(Drawable var1);
}   

哈哈 开个玩笑。
t01bb3bd17cc3655753

我们可以看到,使用此接口,可以获取已加载完的图像的位图,如发生故障,是由于误差本身所带来具有错误状态的图形,如果为Picasso.提供图片,则可以为占位符绘制图形。

现在该到使用由Compose framwork提供的新的给力工具的时候了,也就是onCommit lambd.


onCommit效果是每次对已改变了的效果进行回调输入的一种生命周期效果。

听起来好像有点复杂……那就简化一下!

事实上,这是一个带参数的lambda,它提供了一个范围,你可以在这个范围中运行代码,并在必要时进行处理,这在处理异步操作(如从Internet下载图像)时非常方便。

我们可以这样用:

哇……这要接收好多代码……我解释一下:

// We declare remembered values first, to avoid redoing the
 work on recomposition 
var image by remember { mutableStateOf<ImageAsset?>(null) } 
var drawable by remember { mutableStateOf<Drawable?>(null) }

首先,我们得强调需要记住的值,即我们要从网络上获取的图像资源素材,以及一个图片,它可能是从我们的app资源中获取一个占位符或错误图像。

它们将帮助我们通过重组来维持状态。

此处了解有关Jetpack Compose中 remember 关键字和状态的更多信息。

然后,我们宣布onCommit lambd能提供一个供我们可以执行异步代码的范围:

onCommit(url) { 
val picasso = Picasso.get() // 
We load the image to this target
 // | 
// v 
val target = 
object : Target { override fun onBitmapLoaded(bitmap: Bitmap?, from: 
Picasso.LoadedFrom?) {
 //Here we get the loaded
 image image = bitmap?.asImageAsset() 
}
 }

我们还解决了代码中的占位符和错误,并需要处理此块中的代码。

onDispose { 
image = null 
drawable = null 
picasso.cancelRequest(target)
 }

最后,我们可以将获得的图像加载到目标中:

picasso
 .load(url)
 .into(target) // <- See how we load into the target?

因此,在我们退出 onCommit 代码块后,我们得到了一个图像和一个可绘制对象,最后就可以组成我们的小部件了:

if (image != null) {
// Image is a pre-defined composable that lays out and 
draws a given [ImageAsset]. 
Image(asset = image, modifier = modifier) 
}

如果我们需要一个占位符图像,直到该图像加载完毕,或者为防止在绘制图像过程中出现错误,我们可以添加以下代码以在Canvas小部件上对其进行绘制:

else if (drawable != null) {
 // Canvas is a pre-defined composable 
Canvas(modifier = modifier) { 
drawIntoCanvas { canvas -> 
drawable.draw(canvas.nativeCanvas) 
 }
 } 
}

就是这样了! 我们现在就能有一个可以从网上下载图像并能显示出来的小部件啦! 小部件完整的代码是:

@Composable
 fun NetworkImage(url: String?, modifier: Modifier) {

 var image by remember { mutableStateOf<ImageAsset?>(null) } 
var drawable by remember { mutableStateOf<Drawable?>(null) }

 onCommit(url) {
 val picasso = Picasso.get() 

val target = object : Target { 
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
 drawable = placeHolderDrawable
 } 
override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { drawable = errorDrawable } override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
 image = bitmap?.asImageAsset() 
}
 } 

picasso
  .load(imagePath)
 .placeHolder(R.drawable.placeholder) .error(R.drawable.error)
 .into(target) 
onDispose { 
image = null
 drawable = null 
picasso.cancelRequest(target)
 }
 } 

if (image != null) {
 Image(asset = image, modifier = modifier) } 
else if (theDrawable != null) {
 Canvas(modifier = modifier) { 
drawIntoCanvas { canvas -> 
drawable.draw(canvas.nativeCanvas)
 }
 }

感谢亲的阅读!

希望你能立即创建一个复杂小部件呢!

WRITTEN BY

Ziv Kesten

ProAndroidDev

原文链接

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
134
相关专栏
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。