使用 Jetpack Compose 异步加载图像

作为一名 Android 开发者,我经常在开心和沮丧两种情绪之间来回横跳。

Android 开发者能创造出很多优质的 app,设计出连贯清晰的基础代码,给用户美好的使用体验。

但如果你问经验丰富的 Android 开发者,Android 开发的痛点是什么,相信大多数开发者都会给出相同的答案:

用 XML 文件搭建 UI。

启动 Jetpack Compose

Jetpack Compose 是一种用 Android Kotlin 编写 UI 代码的新的声明式方法,有很多资源可供参考,我推荐这个由 Leland Richardson(Android 工具包团队的主要工程师之一)制作的 视频,另外,还有很多代码实验室 可供参考。

创建图像

Compose 中的所有 UI 组件都是微件,而且这些微件基本上都是带有 Composable 注解的函数。

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

下面是 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);
    }   

如上所示,使用此接口,我们可以获取已加载完的图像的位图。如果发生故障,可以获得一个错误状态的可绘制图像,如果为 Picasso 提供图像,则可以获得一个占位符的可绘制图像。

是时候使用 Compose 框架提供的工具啦——onCommit lambd


onCommit 效果是一种生命周期效果,每次该效果的输入发生改变,就会执行回调。

这样听起来似乎有点复杂……

简单来说,这是一个带参数的 lambda,它提供了一个范围,你可以在这个范围中运行代码,并在必要时弃置不用,这样处理异步操作(例如从互联网下载图像)非常方便。

我们可以这样用:

// 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) }
onCommit(url) {
    val picasso = Picasso.get()
// We load the image to this target
//           |
//           v

    val target = object : Target {
        override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
        
            //we could use the drawable below
            
            drawable = placeHolderDrawable
        }

        override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) {
            //Handle the exception here
            drawable = errorDrawable
        }

        override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
        
           //Here we get the loaded image
           
          image = bitmap?.asImageAsset()
        }
    }
    picasso
            .load(url)
            .into(target) // <- See how we load into the target?
            
    //And eventually we can clear the resources
    onDispose {
        image = null
        drawable = null
        picasso.cancelRequest(target)
    }
}

太多代码了,我解释一下:

    // 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)
            }
        }
    }
}

感谢大家的阅读!

希望大家读了这篇文章后,都能自行创建复杂微件~~

原文作者:Ziv Kesten
原文链接:Async image loading — The Jetpack Compose way | by Ziv Kesten | ProAndroidDev

注册登录 后评论
    // 作者
    声网技术社区 发布于 声网开发者社区
    • 0
    // 本帖子
    // 相关帖子
    Coming soon...
    • 0
    使用 Jetpack Compose 异步加载图像声网技术社区 发布于 声网开发者社区