Android Jetpack Compose帧动画(Animatable、AVD、Lottie)

前言

在 Jetpack Compose 中实现多帧动画(即逐帧动画,类似 GIF 的效果),主要有以下几种方式:

方法 适用场景 备注
mutableStateOf + LaunchedEffect 任意多帧 PNG/JPG 资源 最灵活、最常用
AnimatedImageVector Animated Vector Drawable 仅限矢量动画
AndroidView + AnimationDrawable 兼容旧逻辑 不推荐用于新项目
Lottie 复杂动画

使用Animatable + LaunchedEffect

手动控制帧切换

Jetpack Compose 本身没有直接提供“逐帧动画”的内置组件,但你可以通过组合 remembermutableStateOfLaunchedEffect 和协程来实现。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Composable
fun FrameAnimation(
frameIds: List<Int>, // 图片资源 ID 列表,如 listOf(R.drawable.frame1, R.drawable.frame2, ...)
durationPerFrame: Int = 100, // 每帧持续时间(毫秒)
isPlaying: Boolean = true,
modifier: Modifier = Modifier
) {
val currentFrame = remember { mutableStateOf(0) }
val totalFrames = frameIds.size

LaunchedEffect(isPlaying) {
if (!isplaying) return@LaunchedEffect
while (true) {
delay(durationPerFrame.toLong())
currentFrame.value = (currentFrame.value + 1) % totalFrames
}
}

Image(
painter = painterResource(id = frameIds[currentFrame.value]),
contentDescription = null,
modifier = modifier
)
}

注意💡:

你需要提前将所有帧作为 drawable 资源放入项目中。

优化建议

  • 预加载图片:避免在每一帧都调用 painterResource(它内部有缓存,但最好确保资源已加载)。
  • 控制生命周期:通过 isPlaying 参数控制动画启停,避免内存泄漏。
  • 使用 rememberUpdatedState:如果 frameIdsdurationPerFrame 可能变化,需用 rememberUpdatedState 包裹以避免重启协程。

使用AnimatedImageVector

仅适用于 VectorDrawable 动画

Animated Vector Drawable 是一种基于 XML 的 Android 原生动效格式,结合了矢量图形与属性动画,适合实现轻量、流畅、可缩放的图标动画。

示例代码

如果你的动画是 Animated Vector Drawable(AVD),可以使用:

1
2
3
4
5
6
7
8
val image = AnimatedImageVector.animatedVectorResource(R.drawable.animated_vector)

var atEnd by remember { mutableStateOf(false) }

Image(
painter = remember { image.painterFor(atEnd) },
contentDescription = null
)

但这只适用于 Android 原生支持的 AnimatedVectorDrawable,不适用于普通位图(PNG/JPG)的逐帧动画。

生成工具

Shape Shifter

使用传统View动画(不推荐)

虽然可以在 Compose 中嵌入 AndroidView 并使用传统 ImageView + AnimationDrawable,但这违背了 Compose 的声明式理念,且性能和状态管理不如纯 Compose 方案。

使用动画库Lottie

Jetpack Compose 中使用 Lottie(由 Airbnb 开发的动画库)非常方便,官方提供了对 Compose 的原生支持。

在 Jetpack Compose 中使用 Lottie 只需三步:

  1. 添加 lottie-compose 依赖
  2. 放入 .jsonres/raw
  3. rememberLottieComposition + animateLottieCompositionAsState + LottieAnimation

它比 AVD 更强大,适合复杂动效;而 AVD 更适合轻量、原生图标动画。两者可根据场景搭配使用。

准备 Lottie 动画文件

AE插件下载

LottieFiles 插件(原 Bodymovin)

LottieFiles for Adobe After Effects: Streamline your animation workflow

添加依赖

在你的 build.gradle(Module 级别)中添加 Lottie Compose 依赖:

Android - Jetpack Compose

检查最新版本:https://github.com/airbnb/lottie-android

1
2
3
dependencies {
implementation("com.airbnb.android:lottie-compose:6.6.6")
}

💡 截至 2025 年 12 月,最新稳定版通常是 6.x,支持 Compose 1.5+ 和 Android API 21+。

JSON+图片

如果导出的文件是JSON+图片文件夹

导入动画文件

Lottie-Compose 不会自动从网络或任意路径加载图片,必须将图片放在 Android 可访问的资源目录 中。

推荐做法:把JSON+图片文件夹放入 assets/ 目录

在你的 Android 项目中创建或使用 src/main/assets/ 目录。

将整个images/文件夹(或里面的 PNG 文件)复制进去:

1
2
3
4
5
1app/src/main/assets/
2 ├── vs_success.json
3 └── images/
4 ├── img_0.png
5 └── img_1.png

注意:

JSON 文件也建议放在 assets/ 中,方便统一管理。

在 Compose 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.rememberLottieComposition

@Composable
fun LottieWithImages() {
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.Asset("vs_success.json"), // JSON 在 assets/ 下
imageAssetsFolder = "images" // 对应 JSON 中的图片目录名
)

LottieAnimation(
composition = composition,
modifier = Modifier.width(600.dp),
)
}

矢量图动画

导入动画文件

矢量动画导出只有一个JSON文件

将你的 .json 动画文件(由 After Effects + LottieFiles 导出)放入:

1
app/src/main/res/raw/your_animation.json

文件名建议全小写,如 loading.jsonsuccess_check.json

在 Compose 中使用

基础用法

基础用法(自动播放、循环)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.compose.*

@Composable
fun LottieExample() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.your_animation))
val progress by animateLottieCompositionAsState(composition)

LottieAnimation(
composition = composition,
progress = { progress }, // 或直接传 progress
modifier = Modifier.size(200.dp)
)
}

控制播放行为

控制播放行为(暂停、反向、速度等)

你可以通过 LottieAnimatable 或控制 animateLottieCompositionAsState 的参数来实现高级控制。

示例:点击播放/暂停

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Composable
fun ControllableLottie() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart_anim))
var isPlaying by remember { mutableStateOf(true) }

val progress by animateLottieCompositionAsState(
composition = composition,
isPlaying = isPlaying,
restartOnPlay = false // 不从头开始
)

Column {
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier.size(150.dp).clickable {
isPlaying = !isPlaying
}
)
Text("点击动画 ${if (isPlaying) "暂停" else "播放"}")
}
}

常用参数说明

参数 说明
composition 通过 rememberLottieComposition 加载的动画资源
progress 动画进度(0f ~ 1f),通常由 animateLottieCompositionAsState 提供
modifier 控制大小、点击等
enableMergePaths 是否启用合并路径(某些动画需要设为 true,但会禁用硬件加速)
dynamicProperties 动态修改颜色、文本等内容(高级用法)

高级:动态修改颜色

高级:动态修改颜色(Dynamic Properties)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val dynamicProperties = remember {
LottieDynamicProperties(
listOf(
LottieDynamicProperty.Color(
keyPath = arrayOf("Layer", "Shape Group", "Fill 1"),
color = Color.Red.value
)
)
)
}

LottieAnimation(
composition = composition,
progress = { progress },
dynamicProperties = dynamicProperties
)

注意:keyPath 需要根据你的 Lottie JSON 结构确定,可用 LottieFiles 预览器 查看图层名。

性能提示

  • Lottie 在 Compose 中默认使用 软件渲染,复杂动画可能影响性能。
  • 避免在列表(LazyColumn)中频繁创建 rememberLottieComposition,应提升到 ViewModel 或使用 derivedStateOf 缓存。
  • 对于简单图标动画,仍可考虑 AnimatedVectorDrawable(更轻量)。

官方资源