Jetpack Compose加载动态WebP

前言

动态 WebP 是 Google 推出的一种支持动画的图像格式,它在保持较小文件体积的同时提供高质量的图像和动画效果。

相比传统的 GIF 格式,WebP 动画通常体积更小、色彩更丰富(支持 24 位 RGB + 8 位 Alpha 透明通道),因此在移动端(尤其是 Android)中被广泛用于加载轻量级动画。

Android 对动态 WebP 的原生支持

  • Android 4.0(API 14) 开始支持静态 WebP。
  • Android 5.0(API 21) 起开始原生支持动态 WebP(即动画 WebP)。

WebP和APNG

对比

实际测试下来

在保持图片原尺寸显示的时候,WebP和APNG画质基本一致。

图片放大的前提下,APNG相比WebP效果更好,WebP出现锯齿感。

相同大小下WebP比APNG文件更小。

WebP解码的效率优于APNG。

最佳实践

  • UI 动画(如加载图标、表情) → 优先考虑 APNGLottie(矢量动画)
  • 网络图片、内容图片 → 使用 WebP(尽量无损或高质量有损)

添加依赖

Android API28以上默认就是支持动态WebP的。

这里添加coil来加载图片。

安装依赖

1
2
implementation("io.coil-kt:coil-compose:2.7.0")
implementation("io.coil-kt:coil-gif:2.7.0")

Compose封装

组件封装

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
import android.graphics.Bitmap
import android.os.Build.VERSION.SDK_INT
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.request.ImageRequest


@Composable
fun ZImageWebP(
assetPath: String,
modifier: Modifier = Modifier.fillMaxSize(),
) {
val context = LocalContext.current

val imageLoader = ImageLoader
.Builder(LocalContext.current)
.components {
if (SDK_INT >= 28) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
}
.bitmapConfig(Bitmap.Config.ARGB_8888)
.build()
AsyncImage(
model = ImageRequest.Builder(context)
.data("file:///android_asset/$assetPath")
.build(),
imageLoader = imageLoader,
contentDescription = null,
modifier = modifier.graphicsLayer(),
contentScale = ContentScale.Inside // 设置内容缩放模式
)
}

缩放方式

ContentScale枚举值:

  • FillBounds

    拉伸图像以完全填充目标边界,不保持宽高比。

    可能导致图像变形。

  • Fit

    缩放图像以使其完全适应目标边界,同时保持宽高比。

    图像的整个内容可见,但可能在某一边有空白(letterboxing/pillarboxing),如果图片较小会放大图像。

  • Crop

    缩放图像以覆盖整个目标边界,保持宽高比。

    图像可能会被裁剪(超出部分不可见)。

  • None

    不进行任何缩放;图像以其原始像素尺寸显示。

    如果原始尺寸大于容器,则可能被裁剪;如果更小,则只占据一部分空间。

  • Inside

    类似于 Fit,但仅在图像大于容器时才缩小;如果图像小于容器,则保持原尺寸。

    始终保持宽高比,且不会放大图像。

  • FillHeight

    缩放图像以填满容器的高度,宽度按比例缩放。

    可能导致宽度超出容器(会被裁剪)。

  • FillWidth

    缩放图像以填满容器的宽度,高度按比例缩放。

    可能导致高度超出容器(会被裁剪)。

准备图片

我这里把图片放在了app/src/main/assets/apng目录下。

原因

不要将APNG资源放置到drawable或者mipmap目录下!

在Android app release构建过程中, aapt工具会压缩修改APNG资源的帧信息, 会导致播放不正常. 因此请将APNG资源放置到工程内的raw或者assets目录内.

调用

1
2
3
4
5
6
7
Box(
modifier = Modifier
.size(300.dp)
.align(Alignment.Center)
) {
ZImageWebP("apng/vs_pop_success.webp")
}