[译]在 Jetpack Compose 中使用 Coil 加载图片

cover

加载图片,特别是通过网络加载,可能是一个令人生畏的过程。我们需要处理各种格式、慢速连接、缓存以供后续使用等问题。

幸运的是,有一些第三方库可以为我们承担这些繁重的工作。

对于这项任务,我选择的库是由 Instacart 开发的 coil 图片加载库。除了快速和轻量级之外,它能很好地与 Jetpack Compose 配合使用,并使用协程,这与我的 Android 开发工作流程非常匹配。

在本文中,我们将探讨如何将 coil 库集成到 Jetpack Compose 中。

开始之前

我们需要将 coil compose 库添加到我们的项目中。在你的应用程序 build.gradle 文件的依赖项中添加以下行。

implementation("io.coil-kt:coil-compose:2.2.2")

别忘了在 AndroidManifest.xml 中添加互联网权限

<manifest 
	...
    <uses-permission android:name="android.permission.INTERNET" />

基础知识

要使用 coil 加载图片,我们只需要 AsyncImage

AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = "这是一个示例图片"
)

就这么简单!

有了这段代码,coil 会处理通过网络加载图片、显示图片并自动缓存以供后续使用。虽然现在看起来很简单,但 AsyncImage 组件提供了扩展功能。

AsyncImage 自定义

让我们来看看它提供的一些参数。

1. model

model 参数接收 Any 类型的对象,可以处理各种输入。在这种情况下,我传入了一个图片的 URL,但它也可以识别 android 资源(drawables 和 vector drawables)、文件、字节数组等。

model 参数还可以接收 ImageRequest 对象。这为我们提供了比仅传入 URL 更多的功能。

ImageRequest

以下是如何使用 ImageRequest。我们用构建器替换 URL 字符串。

AsyncImage(  
    model = ImageRequest.Builder(LocalContext.current)  
        .data("https://example.com/image.jpg")  
        .build(),  
    contentDescription = "ImageRequest 示例",
)

这一开始可能看起来像是不必要的代码,但它可以让我们更好地控制图片的加载方式。说实话,Image request 构建器有许多我不熟悉的选项。我发现非常有用的选项是缓存键和缓存策略。

缓存键允许我们为图片的缓存位置设置一个键,像这样:

...
.diskCacheKey("halloween_image_$date_$index")
.build()

这对于识别缓存中的图片很有用。例如,我们可能在缓存中有一些当前不需要的万圣节图片。由于我们在键中保存了 halloween,我们可以删除缓存中具有该键的所有图片。你也可以设置 memoryCacheKey。

缓存策略用于激活/停用图片源,像这样:

.networkCachePolicy(CachePolicy.ENABLED)  
.diskCachePolicy(CachePolicy.DISABLED)  
.memoryCachePolicy(CachePolicy.ENABLED)

当你想限制特定图片对某个缓存的读/写访问时,这些很有用。CachePolicy.ENABLEDCachePolicy.DISABLED 分别允许或拒绝完全的读/写访问,但我们也可以使用 CachePolicy.READ_ONLYCachePolicy.WRITE_ONLY 分别限制它们。

关于通过 ImageRequest 可用的所有其他可能性,请查看源代码

2. contentDescription

content description 简单地描述了显示的图片内容。这类似于网站上图片的 alt 标签,对于可访问性来说非常重要,所以尽量在需要的地方包含它。

contentDescription = "图片的描述,用于可访问性",

在不必要的情况下(例如父视图处理可访问性),将其设置为 null

3. placeholder & error

这两个参数定义了在网络图片加载或失败时使用的备用图片。通常,这些是你资源文件夹中的本地图片。

placeholder = painterResource(id = R.drawable.loading_placeholder),  
error = painterResource(id = R.drawable.image_error),

4. contentScale

这定义了源图片如何根据你想要显示的内容适应目标。例如,我们可能希望代码示例截图从边到边显示其内容,但人像可以裁剪到中心的主题。

contentScale = ContentScale.Crop,

gif

5. alignment

Alignment 与 contentScale 密切相关。默认情况下,contentScale 会裁剪到中心。使用这个,我们可以裁剪到所有 9 个 Alignment 锚点。

alignment = Alignment.BottomStart,

除此之外,我们可以创建自己的自定义 Alignment 锚点,这样我们就可以聚焦于图片的特定部分。

6. filterQuality

这定义了用于缩放位图以供显示的采样算法。当你需要更高质量的图片时可以使用它。但我反其道而行之,通过将滤镜质量降到最低来显示像素艺术。

filterQuality = FilterQuality.None

这样,我就可以显示一个 16x16 的图片,并且它会显示得很清晰。

cover

动画

Coil 提供了一个基本的淡入淡出动画,我们可以使用 ImageRequest 构建器启用它。

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .crossfade(true)
        .build(),
    contentDescription = "默认淡入淡出示例"
)

crossfade

这种方式快速简单,当你只需要图片加载时的淡入淡出效果时可以使用。不幸的是,coil 不提供任何更高级的动画。

为此,我们需要转而使用 rememberAsyncImagePainter 函数,而不是 AsyncImage。

AsyncImage 类似,我们传入一个可以是 URL、ImageRequest 等的 model。但它不是返回一个准备显示的图片,而是返回一个我们可以用来显示图片的状态。

val painter = rememberAsyncImagePainter("https://example.com/image.jpg")

Image(
    painter = painter,
    contentDescription = "使用异步图片绘制器的示例"
)

这个示例只是显示图片,但我们可以使用返回给我们的状态来响应图片加载状态并相应地制作动画。这里是一个模仿默认淡入淡出的示例。

val painter = rememberAsyncImagePainter("https://example.com/image.jpg")  
val state = painter.state  
  
val transition by animateFloatAsState(  
    targetValue = if (state is AsyncImagePainter.State.Success) 1f else 0f
)  
Image(  
    painter = painter,  
    contentDescription = "基于绘制器状态的自定义过渡",  
    modifier = Modifier
        .alpha(transition)  
)

但现在我们可以做得比简单的淡入淡出更好!

.saturation(transition)  
.scale(.8f + (.2f * transition))  
.graphicsLayer { rotationX = (1f - transition) * 5f }  
.alpha(min(1f, transition / .2f))

在这个自定义动画中,我使用前一个代码示例中相同的 transition 变量来制作饱和度、缩放和旋转的动画。我还缩短了 alpha 动画,以便其他动画更加明显。

我们可以用状态做的最后一件事是添加一个漂亮的加载动画,代替我在前一节中提到的静态 placeholder drawable。

if (state is AsyncImagePainter.State.Loading) {  
    LoadingAnimation()  
}

有了这些,我们就能得到这个漂亮的动画!

final_animation

感谢阅读,祝你好运!