---
title: "Lottie Composeの使い方｜Jetpack Composeでの再生制御・dotLottie・状態遷移【6.7.1】"
url: "https://www.issoh.co.jp/tech/details/4869/"
published: 2025-01-16
updated: 2026-11-03
categories: ["Kotlin"]
publisher: "株式会社一創"
---

# Lottie Composeの使い方｜Jetpack Composeでの再生制御・dotLottie・状態遷移【6.7.1】

Lottie ComposeはAirbnbが公開しているLottieのJetpack Compose向けAPIで、After Effectsなどから書き出したJSONアニメーションをコンポーザブルとして描画します。この記事では、Gradleへの追加から再生制御、読み込み中から成功表示への切り替え、.lottieファイルの扱い、再生されないときの原因までを、バージョン6.7.1のソースで確かめた引数名・既定値とともに整理します。

## まとめ

Lottie Composeの導入は `com.airbnb.android:lottie-compose` を1行追加するだけで済みます。このアーティファクトは `lottie` 本体を `api` 依存で内包しているため、本体を別途書き足す必要はありません。最小構成は `rememberLottieComposition` でJSONを読み、`LottieAnimation` に渡す2手です。

時間を取られやすいのは機能追加ではなく挙動の解釈です。再生されない症状は、コードではなく端末設定が原因のことがあります。Lottieは既定で速度を端末のアニメータスケールで割るため、開発者オプションやバッテリーセーバーでスケールが0になった端末では、アニメーションが一瞬で最終フレームへ飛びます。

最新版は6.7.1（2025年10月31日公開）で、2026年11月時点まで1年近く新しいリリースは出ていません。.lottie（dotLottie）形式は1ファイル1アニメーションなら読めますが、ステートマシンやテーマは扱えません。Compose Multiplatformにも対応していないため、iOSやデスクトップと同じコードで動かしたい場合は別ライブラリを選ぶ判断になります。

## Lottie Composeの守備範囲と6.7.1時点の依存関係

### lottie-composeとlottie本体の依存関係

Lottie Composeは独立したライブラリではなく、Android版Lottieのレンダリングエンジンをそのまま使い、Compose向けのコンポーザブルとステート管理を被せたモジュールです。リポジトリの `lottie-compose/build.gradle` には `api project(':lottie')` と書かれており、依存は推移的に公開されます。

`lottie-compose` を追加すれば `LottieComposition` や `LottieProperty` といった本体側のクラスもそのまま参照できます。両方を明示的に書いているサンプルを見かけますが、バージョンがずれる事故を増やすだけなので不要です。

### 対応環境と6.7.1以降のリリース状況

ビルド設定上の対応範囲は `minSdk 21`、`compileSdk 36`、`targetSdk 36` です。Android 5.0以降であれば動作対象に入ります。直近のリリースは[lottie-androidのCHANGELOG](https://github.com/airbnb/lottie-android/blob/master/CHANGELOG.md)に次のように記録されています。

| バージョン  | 公開日        | 主な変更                       |
| ------ | ---------- | -------------------------- |
| 6.7.1  | 2025-10-31 | 巨大なBitmap生成への保護            |
| 6.7.0  | 2025-10-27 | 3Dアニメーションの初期対応             |
| 6.6.10 | 2025-10-02 | RTL対応                      |
| 6.6.9  | 2025-09-16 | MULTIPLYブレンド描画の修正（API 34+） |

[Maven Centralのメタデータ](https://repo1.maven.org/maven2/com/airbnb/android/lottie-compose/maven-metadata.xml)でも、2026年11月3日時点の最新は6.7.1のままです。GitHubのmasterには2026年2月15日に「ウィンドウごとのUIスレッドでの描画対応」が入っていますが、リリースには含まれていません。この記事のAPIと既定値は6.7.1のソースで確かめたものです。版を上げるときはCHANGELOGで該当する引数の変更有無を見てから更新してください。なお公式ドキュメントのURLは `airbnb.io/lottie` から `lottie.airbnb.tech` へ301リダイレクトされます。

## Gradleへの追加とバージョンカタログでの管理

バージョンカタログを使う構成では、`libs.versions.toml` に次のように書きます。

```
[versions]
lottie = "6.7.1"

[libraries]
lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottie" }
```

モジュール側の `build.gradle.kts` では1行です。

```
dependencies {
    implementation(libs.lottie.compose)
}
```

Lottie Composeの版番号はCompose BOMとは別体系で、BOMの版に合わせて選ぶ必要はありません。[6.7.1のPOM](https://repo1.maven.org/maven2/com/airbnb/android/lottie-compose/6.7.1/lottie-compose-6.7.1.pom)は `compose-bom` 2024.02.01とKotlin標準ライブラリ1.9.22を参照しています。Gradleは同じライブラリの版が衝突すると新しい方を採るため、これより新しいBOMを使うアプリではアプリ側のCompose版がそのまま使われ、古いComposeを固定しているアプリだけがLottieの追加で2024.02.01相当へ引き上げられます。BOMの運用そのものは[Jetpack Composeとは？宣言的UIの仕組みとBOM運用・採用判断を解説](/tech/details/16030/)で扱っています。

## アニメーションの読み込みと表示

### LottieCompositionSpecが受け取れる6種類の入力元

読み込み元は `LottieCompositionSpec` というsealed interfaceで表現され、実装は6つのvalue classです。`RawRes`（res/rawのリソースID）、`Asset`（assetsのファイル名）、`Url`（HTTP経由）、`File`（端末上のファイルパス）、`JsonString`（JSON文字列そのもの）、`ContentProvider`（Uri）です。

実務でまず使うのは `RawRes` です。ビルド時にリソースIDへ解決されるため、ファイル名のタイプミスがコンパイルエラーになります。サーバーから差し替えたい場合だけ `Url` を選びます。

```
@Composable
fun LoadingAnimation() {
    val composition by rememberLottieComposition(
        LottieCompositionSpec.RawRes(R.raw.loading)
    )
    LottieAnimation(
        composition = composition,
        iterations = LottieConstants.IterateForever,
        modifier = Modifier.size(160.dp),
    )
}
```

`LottieConstants.IterateForever` の実体は `Integer.MAX_VALUE` で、無限ループではなく「事実上終わらない回数」の指定です。途中で止めたい場合は後述の再生制御を併用します。

### .lottie（dotLottie）・ZIP・tgsファイルを読み込む条件

6.6.0で解析処理がZIP前提に統一され、JSON以外にZIP、.lottie（中身はZIP）、Telegramスタンプの.tgs（中身はgzip圧縮JSON）も読めます。ただし形式の判定方法が入力元ごとに違います。

- `RawRes`・`Asset`・`ContentProvider`：ファイル先頭のマジックバイトでZIPかgzipかを判定する。拡張子は問わない。
- `Url`：Content-Typeが `application/zip` 系か、URL末尾が `.lottie` ならZIPとして読む。
- `File`：ファイル名の末尾が `zip` か `tgs` かだけで分岐し、それ以外はJSONとして読む。

落とし穴は `File` です。ダウンロードした `anim.lottie` をそのままのファイル名で渡すとJSONとして解析され、読み込みに失敗します。保存時に拡張子を `.zip` にするか、`ContentProvider` で渡してください。

もう1つの制約はZIPの中身の扱いです。[LottieCompositionFactoryの実装](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java)は `manifest.json` を読み飛ばし、ZIP内で `.json` を見つけるたびに解析結果を上書きします。アニメーションを1本だけ含む.lottieなら問題ありませんが、複数のアニメーションやテーマ・ステートマシンの定義を詰めた.lottieでは、どのJSONが残るかを制御できません。そうしたファイルを使う場合は後述のdotlottie-androidを選びます。

### res/raw-nightによるダークモード用アニメーションの切り替え

ライト用とダーク用で別のJSONを用意しているなら、`res/raw/loading.json` と `res/raw-night/loading.json` に同名で置くだけで切り替わります。`RawRes` のキャッシュキーは `rawRes_night_<id>` と `rawRes_day_<id>` に分かれているため、昼の解析結果が夜に使い回されることはありません。既定ではダークモードの切り替えでActivityが作り直され、`rememberLottieComposition` が夜のリソースを読み直します。

この方法が効かない構成は2つあります。1つは、マニフェストの `android:configChanges` に `uiMode` を入れてActivityを作り直さない場合です。`rememberLottieComposition` は `spec` と `cacheKey` だけをキーに結果を保持するため、構成が夜に変わっても読み込み済みの昼のアニメーションが表示され続けます。もう1つは、アプリ内のテーマ切り替えをComposeの状態だけで持ち、構成の `uiMode` を変えていない場合で、`raw-night` 自体が選ばれません。色を変えるだけなら、JSONを2本持つより後述の動的プロパティでテーマ色を流し込む方が管理が楽です。

### LottieAnimationの2系統のオーバーロードの使い分け

`LottieAnimation` の宣言はソース上4本ありますが、うち1本は `@Deprecated("Pass progress as a lambda instead of a float. This overload will be removed in the next release.")`、もう1本は `DeprecationLevel.HIDDEN` のバイナリ互換用です。実際に選ぶのは2系統に絞られます。

- `progress: () -> Float` を受け取る版：進捗を呼び出し側が管理する。ジェスチャーやダウンロード進捗に連動させる場合はこれ。
- `isPlaying` や `iterations` を直接受け取る版：内部で駆動まで面倒を見る。単純な再生だけならこれで足ります。

進捗がラムダである点には理由があります。値で受け取ると毎フレームの進捗変化がコンポーズ関数の再実行を招きますが、ラムダなら読み取りが描画フェーズまで遅延され、再コンポーズを回避できます。[CHANGELOG\_COMPOSE](https://github.com/airbnb/lottie-android/blob/master/CHANGELOG%5FCOMPOSE.md)の5.2.0の項に、この変更が破壊的変更として記録されています。

### 読み込み中と失敗時の状態分岐

戻り値の `LottieCompositionResult` は `State<LottieComposition?>` を継承しつつ `isLoading`、`isFailure`、`isSuccess`、`error` を公開しています。`by` で受けると中身の `LottieComposition?` だけが取れるので、状態を見たいときは委譲せずに受け取ります。

```
val result = rememberLottieComposition(
    LottieCompositionSpec.Url("https://example.com/anim/success.json")
)
when {
    result.isLoading -> CircularProgressIndicator()
    result.isFailure -> Text("アニメーションを読み込めませんでした")
    else -> LottieAnimation(
        composition = result.value,
        iterations = LottieConstants.IterateForever,
    )
}
```

ネットワーク読み込みでは `onRetry` も使えます。型は `suspend (failCount: Int, previousException: Throwable) -> Boolean`、既定値は常に `false`（再試行しない）です。無条件に `true` を返すと再試行が止まらなくなるため、上限回数の判定を必ず入れてください。ユーザー操作で再試行させるなら `rememberLottieRetrySignal` を組み合わせます。

### 解析結果のキャッシュ上限とcacheKey

解析済みの `LottieComposition` はメモリ上のLRUキャッシュに保持され、上限は20件です。`cacheKey` を省略すると入力元から自動でキーが作られ、同じプロセス内でキャッシュから追い出されていなければ、画面を開き直しても再解析は走りません。一覧画面などで20種類を超えるアニメーションを行き来するなら `LottieCompositionFactory.setMaxCacheSize()` で上限を上げ、サーバー側で同じURLの中身を差し替えたときは `LottieCompositionFactory.clearCache(context)` でメモリとネットワークの両方のキャッシュを捨てます。キャッシュさせたくない一時的なアニメーションには `cacheKey = null` を渡します。

## 再生制御とユーザー操作への連動

### animateLottieCompositionAsStateの主要な引数

再生の細かい制御は `animateLottieCompositionAsState` が担います。戻り値の `LottieAnimationState` は `State<Float>` なので、`by` で進捗の `Float` として受け取れます。

```
val composition by rememberLottieComposition(
    LottieCompositionSpec.Asset("success.json")
)
val progress by animateLottieCompositionAsState(
    composition = composition,
    speed = 1.5f,
    iterations = 3,
    reverseOnRepeat = true,
)
LottieAnimation(composition = composition, progress = { progress })
```

`reverseOnRepeat` を `true` にすると、繰り返しのたびに逆再生されて往復します。`restartOnPlay` の既定は `true` で、`isPlaying` が `false` から `true` へ戻ったときに先頭まで巻き戻る挙動です。途中から再開したいなら `false` を指定してください。

### LottieClipSpecによる区間・マーカー指定

アニメーション全体ではなく一部だけを再生したい場合は `LottieClipSpec` を渡します。サブクラスは4つあり、内部ではいずれも進捗の最小値・最大値へ変換されます。

| 指定方法                    | 引数                       | 用途            |
| ----------------------- | ------------------------ | ------------- |
| LottieClipSpec.Frame    | min / max / maxInclusive | フレーム番号で区切る    |
| LottieClipSpec.Progress | min / max（0f〜1f）         | 割合で区切る        |
| LottieClipSpec.Markers  | min / max / maxInclusive | 2つのマーカー名で挟む   |
| LottieClipSpec.Marker   | marker                   | 1つのマーカー区間だけ再生 |

デザイナーがマーカー名を維持してくれる取り決めがあるなら、マーカー指定を選びます。フレーム番号で書くと、アニメーションを作り直すたびにコード側の数値を直すことになります。区間が1つに収まるなら `Marker`、開始と終了を別々のマーカーで挟むなら `Markers` です。

### スライダーやスクロールに進捗を連動させる実装

インタラクティブな挙動は、専用APIではなく進捗を自分で渡すことで実現します。`progress` のラムダに、任意のステートを返すだけです。

```
var sliderValue by remember { mutableFloatStateOf(0f) }
val composition by rememberLottieComposition(
    LottieCompositionSpec.RawRes(R.raw.progress_bar)
)
Column {
    LottieAnimation(composition = composition, progress = { sliderValue })
    Slider(value = sliderValue, onValueChange = { sliderValue = it })
}
```

スクロール連動も同じ形で、`LazyListState` の値を0f〜1fへ正規化して返します。操作中はスライダー、指を離したら自動再生、のように2つの進捗を切り替える実装は避けてください。`animateLottieCompositionAsState` の進捗はスライダーと無関係に進んでいるので、切り替えた瞬間に表示が飛びます。操作と自動再生を行き来させるなら、後述の `LottieAnimatable` で `snapTo` と `animate` を使い、1つの進捗を引き継ぎます。

### LottieAnimatableによるコルーチンからの逐次制御

「AをX回再生したあとBへ切り替える」といった逐次的な制御には `rememberLottieAnimatable` を使います。`animate` は suspend 関数なので、完了を待ってから次の処理へ進めます。suspend関数の待ち方やキャンセルの伝わり方は[Kotlin Coroutinesとは？suspendの仕組みと構造化並行性・採用判断](/tech/details/16036/)で解説しています。

```
val composition by rememberLottieComposition(
    LottieCompositionSpec.RawRes(R.raw.check)
)
val animatable = rememberLottieAnimatable()
LaunchedEffect(composition) {
    animatable.animate(composition, iterations = 1)
}
LottieAnimation(
    composition = animatable.composition,
    progress = { animatable.progress },
)
```

描画に渡すのが `animatable.composition` である点が要点です。`LottieAnimatable` は排他制御のため、進行中のアニメーションを `CancellationException` で打ち切ってから次を開始します。開始が同期的とは限らず、`animate` を呼んだ直後に新しいcompositionを直接描画するのは安全ではありません。KDocも「常に `LottieAnimationState.composition` と `LottieAnimationState.progress` に依拠せよ」と明記しています。

## 画面の状態に合わせて再生を切り替える実装

### 読み込み中から成功・失敗の区間への切り替え

1本のJSONに「loading」「success」「error」のマーカーを打ってもらい、画面の状態に応じて区間を差し替える構成がよく使われます。`LaunchedEffect` のキーに状態を入れると、状態が変わるたびに前の `animate` が打ち切られて次の区間が始まります。

```
sealed interface SubmitState {
    data object Loading : SubmitState
    data object Success : SubmitState
    data object Error : SubmitState
}

@Composable
fun SubmitAnimation(state: SubmitState) {
    val composition by rememberLottieComposition(
        LottieCompositionSpec.RawRes(R.raw.submit)
    )
    val animatable = rememberLottieAnimatable()
    LaunchedEffect(composition, state) {
        when (state) {
            SubmitState.Loading -> animatable.animate(
                composition,
                clipSpec = LottieClipSpec.Marker("loading"),
                iterations = LottieConstants.IterateForever,
                cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
            )
            SubmitState.Success -> animatable.animate(
                composition,
                clipSpec = LottieClipSpec.Marker("success"),
                iterations = 1,
            )
            SubmitState.Error -> animatable.animate(
                composition,
                clipSpec = LottieClipSpec.Marker("error"),
                iterations = 1,
            )
        }
    }
    LottieAnimation(
        composition = animatable.composition,
        progress = { animatable.progress },
    )
}
```

読み込み中のループに `LottieCancellationBehavior.OnIterationFinish` を指定しているのが要点です。既定の `Immediately` のままだと、通信が終わった瞬間にループの途中から成功区間の先頭へ飛び、つなぎ目が見えます。[LottieAnimatableの実装](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt)では `OnIterationFinish` のとき再生を `NonCancellable` で包み、キャンセルされても現在の周回を最後まで描いてから抜けます。そのため成功表示は最大でループ1周分遅れます。コードで保証できるのは「周回の終わりまで待つ」ところまでで、つなぎ目が自然に見えるかは素材次第です。ループ区間の最終フレームと成功区間の最初のフレームが同じ絵になるように作ってもらい、1周が長い場合はループ区間を短くしてもらいます。

### タップで切り替わるいいねボタン

ハートが満ちるアニメーションの最初のフレームを「未選択」、最後のフレームを「選択済み」として使えば、Lottieだけでトグルボタンを作れます。選択時は再生し、解除時は先頭へ戻すだけです。

```
@Composable
fun LikeButton(liked: Boolean, onLikedChange: (Boolean) -> Unit) {
    val composition by rememberLottieComposition(
        LottieCompositionSpec.RawRes(R.raw.heart)
    )
    val animatable = rememberLottieAnimatable()
    LaunchedEffect(composition, liked) {
        if (liked) {
            animatable.animate(composition, iterations = 1)
        } else {
            animatable.snapTo(composition, progress = 0f)
        }
    }
    LottieAnimation(
        composition = animatable.composition,
        progress = { animatable.progress },
        modifier = Modifier
            .size(48.dp)
            .toggleable(
                value = liked,
                role = Role.Checkbox,
                onValueChange = onLikedChange,
            )
            .semantics { contentDescription = "いいね" },
    )
}
```

`clickable` ではなく `toggleable` を使うと、TalkBackが「チェックボックス、オン」のように状態を読み上げます。`semantics` の `contentDescription` で何を切り替えるボタンかを名前として与えます。本番では文字列をリソースに移してください。なおこの実装は、画面の再生成で `liked = true` のまま復元されると再生し直します。初回表示で再生させたくない場合は、最初の1回だけ `snapTo(composition, progress = 1f)` で最終フレームに合わせます。

## 動的プロパティによる色の差し替え

JSONを編集せずに描画内容を変えたい場合は `rememberLottieDynamicProperties` を使います。ダークテーマ対応や、テーマカラーへの追随はこれで賄えます。

```
val dynamicProperties = rememberLottieDynamicProperties(
    rememberLottieDynamicProperty(
        LottieProperty.COLOR,
        MaterialTheme.colorScheme.primary.toArgb(),
        "**", "Fill 1",
    ),
)
LottieAnimation(
    composition = composition,
    dynamicProperties = dynamicProperties,
    iterations = LottieConstants.IterateForever,
)
```

第3引数以降は可変長のキーパスで、`"**"` は階層のワイルドカードです。上の例は「どの階層にあっても Fill 1 という名前の塗り」を対象にします。プロパティは型が分かれており、`COLOR` と `STROKE_COLOR` は `Integer`（ARGB）、`COLOR_FILTER` は `ColorFilter`、`GRADIENT_COLOR` は `Integer[]` です。`LottieDynamicProperties` は渡された値の型を見て振り分けるため、型が合わないと反映されないまま何のエラーも出ないことがあります。

キーパスに使う名前はAfter Effects側のレイヤー名です。デザイナーがレイヤー名を変えると静かに壊れるので、差し替え対象のレイヤーは命名を固定する取り決めを先に作っておくと安全です。デザインツール側でLottieを書き出す流れは[Figma Motionとは？使い方・書き出し・無料でできる範囲を解説【2026年10月】](/tech/details/12722/)で扱っています。

## 描画設定とパフォーマンスの判断基準

### RenderMode.AUTOMATICがソフトウェア描画へ切り替わる条件

`renderMode` の既定値は `RenderMode.AUTOMATIC` です。`HARDWARE` と `SOFTWARE` を明示した場合はその指定がそのまま通り、`AUTOMATIC` のときだけ次の条件判定が働いてソフトウェア描画が選ばれます。

- 破線パターンを含み、かつAPIレベル28未満（ハードウェア描画が破線に未対応だった世代）
- マスクとマットの合計が4を超える
- APIレベル25以下（該当端末でのクラッシュ報告が多いため）

実務で効くのは2番目です。マスクやマットを5個以上使ったアニメーションを渡すと、それだけでソフトウェア描画に落ちます。ソースのコメントも「いくつかのアニメーションを試した上でやや恣意的に決めた」と書いており、絶対的な基準ではありません。描画が重いと感じたら `RenderMode.HARDWARE` を明示して実機で比較してください。ただしAPIレベル25以下を対象に含むアプリで一律にHARDWAREを指定するのは避けます。この分岐はクラッシュ回避のために入っています。

### AsyncUpdatesの既定値と実験的位置付け

`asyncUpdates` は `AUTOMATIC`、`ENABLED`、`DISABLED` の3値を取り、有効時は全ノードの進捗計算（`setProgress`）をメインスレッドの外へ逃がします。

見落とされやすいのが既定値の中身です。`LottieAnimation` の既定は `AUTOMATIC` ですが、そのJavadocには「この機能が枯れるまでは DISABLED として振る舞う」と明記されています。既定のままでは非同期更新は働かず、使うには `AsyncUpdates.ENABLED` を明示します。ノード数の多いアニメーションでフレーム落ちが出ているなら試す価値はありますが、API自体が実験的である旨もソースに書かれており、単純なローディング表示に先回りで指定するものではありません。

### rememberLottiePainterをImageやIconに渡す場合の制約

6.3.0から、Lottieを `Painter` として扱う `rememberLottiePainter` が使えます。`Image` や `Icon`、`Modifier.paint` など、Painterを受け取る場所ならどこにでも置けるので、ボタン内のアイコンをアニメーションにしたい場面で便利です。

```
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bell))
val progress by animateLottieCompositionAsState(composition)
Icon(
    painter = rememberLottiePainter(composition = composition, progress = progress),
    contentDescription = "通知",
    tint = Color.Unspecified,
    modifier = Modifier.size(24.dp),
)
```

ただし `LottieAnimation` と違い、`progress` はラムダではなく `Float` で受け取ります。進捗を読むのが呼び出し側のコンポーズ関数になるため、再生中は毎フレーム再コンポーズが走ります。画面全体のアニメーションや一覧の各行では再コンポーズの範囲と回数が増えるので、`LottieAnimation` を使います。`Icon` に渡すときは2点に注意します。`Icon` は既定で `LocalContentColor` の単色で塗りつぶすため、`tint = Color.Unspecified` を渡さないとアニメーションの配色が消えます。また[LottiePainter](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottiePainter.kt)の固有サイズはcompositionの幅と高さ（ピクセル）なので、`Icon` の既定の24dpは読み込み前にしか効かず、読み込み後はcompositionの大きさに広がります。サイズは `Modifier.size` で明示してください。

### 本番に残してはいけないデバッグ用引数

`outlineMasksAndMattes` はマスクとマットを輪郭表示するデバッグ用の引数で、オーバーヘッドが両者の面積に比例して増えるため、KDocに「本番で有効のままにしないこと」と大文字で書かれています。`applyOpacityToLayers` も処理コストが高いので、半透明シェイプの重なりが実際に破綻している画面だけで有効にします。

## 再生されない・崩れるときの原因切り分け

### 端末のアニメータスケール0による即時終了

コードは正しいのにアニメーションが動かない、あるいは一瞬で終わる場合、まず疑うのは端末設定です。[animateLottieCompositionAsStateの実装](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieCompositionAsState.kt)では、実際の速度が次のように計算されています。

```
// Dividing by 0 correctly yields Float.POSITIVE_INFINITY here.
val actualSpeed = if (ignoreSystemAnimatorScale) speed
    else (speed / Utils.getAnimationScale(LocalContext.current))
```

既定では端末のアニメータスケールで速度を割ります。開発者オプションの「アニメータ再生時間スケール」をオフにしている端末や、バッテリーセーバーが動作している端末ではスケールが0になり、実装コメントのとおり速度が `Float.POSITIVE_INFINITY` になります。結果として最終フレームまで一瞬で飛び、静止画に見えます。

切り分けには `ignoreSystemAnimatorScale = true` を渡します。KDocには「無視したければ false に設定」と書かれていますが、上の実装のとおり `true` のときにスケールを掛けない分岐へ入ります。食い違っているので実装側に従ってください。ただし本番アプリで一律に `true` を指定するのは推奨しません。ユーザーがアニメーションを切っているのに再生し続けることになります。

### reduced motionマーカーによる静止表示への切り替え

OS側でアニメーションを減らす設定が有効な利用者には、再生を強行せず静止表示へ落とします。JSONに「reduced motion」という名前のマーカーを置いておくと、アニメーションが無効化された環境では、渡された進捗を無視して静止表示になります。マーカー名は大文字小文字を区別せず、`reduced motion`、`reduced_motion`、`reduced-motion`、`reducedmotion` の4通りが認識されます。

ただしComposeでは静止する位置に注意が要ります。6.7.1の[LottieAnimationの描画処理](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt)は、マーカーの開始フレーム番号（30など）を0〜1の進捗としてそのまま `drawable.progress` に代入しています。1を超えた値は最終フレームに丸められるため、マーカーが0フレーム目にあれば先頭の絵、それ以外の位置なら最終フレームの絵で止まります。View版の `LottieDrawable` はフレーム番号として扱うので、同じJSONでもViewとComposeで静止する絵が変わります。Composeで静止させたい絵は、0フレーム目か最終フレームに置いてもらうのが確実です。

なお `LottieAnimatable.animate` には `ignoreSystemAnimationsDisabled` という引数があり、KDocには「OSでアニメーションが無効でも再生する」と書かれています。しかし[6.7.1の実装](https://github.com/airbnb/lottie-android/blob/v6.7.1/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt)では宣言されているだけで、処理の中から一度も参照されていません。指定しても挙動は変わらず、前項のスケール0の症状も解消しません。

### 画像アセット付きJSONでのimageAssetsFolder未指定

ビットマップを含むアニメーションでは、JSONと同じ場所に画像を置いただけでは読み込まれません。`rememberLottieComposition` の `imageAssetsFolder` は既定値が `null` で、指定しない限りassetsからの画像読み込みはスキップされます。assets配下のフォルダ名を明示的に渡すか、画像ごとZIPか.lottieにまとめて渡してください。ZIP内の画像はファイル名で自動的に結び付けられます。

### テキストレイヤーのフォント解決とfontMap

フォント側の既定値は逆に設定済みで、`fontAssetsFolder` は `"fonts/"`、`fontFileExtension` は `".ttf"` です。assets直下の `fonts` フォルダにAfter Effectsのフォントファミリー名と一致する名前で `.ttf` を置けば自動で解決されます。otfやアプリ内の既存Typefaceを使いたい場合は、`LottieAnimation` の `fontMap` にキーとTypefaceの対応表を渡します。キーはLottieファイル内の `fName`、`fFamily`、`fFamily-fStyle` のいずれかです。段落テキストとして境界ボックスが設定されているレイヤーなら、`clipTextToBoundingBox = true` で高さ方向にはみ出した分を描画しないようにできます。

### 特定端末のクラッシュに対するsafeMode

`safeMode = true` を指定すると描画処理が try/catch で包まれ、例外時はクラッシュせず空フレームが描画されます。原因調査が終わるまでの応急処置であり、例外は握り潰されるので恒久対策にはしません。

## Lottie Composeで足りない場合の別ライブラリ

### Compose Multiplatformで共通化するCompottie

Lottie ComposeはAndroid専用です。`lottie-compose` のビルド設定は `com.android.library` プラグインで構成されており、Kotlin Multiplatformのターゲットを持たないため、`androidMain` 以外から参照できません。JSONファイル自体はプラットフォーム非依存なので、iOSはlottie-ios、Webはlottie-webという分担であれば成立します。共通UIの考え方は[Compose Multiplatformとは？KMPとの違いとiOS対応の現在地・採用判断を解説](/tech/details/16032/)にまとめています。

共通コードで完結させたい場合の選択肢が[Compottie](https://github.com/alexzhirkevich/compottie)です。v2.0からプラットフォームの実装に委譲せず、Kotlinで書かれた独自レンダラで描画しており、Android・iOS・JVM・macOS・Webに対応します。最新は2.3.3（2026年10月5日）で、エクスプレッション対応を外して容量を約半分にした `compottie-lite`、dotLottieとZIPを読む `compottie-dot` などモジュールが分かれています。独自レンダラである以上、After Effectsの全機能が再現される保証はないので、使いたい表現がリポジトリの対応表に含まれるかを先に確認してください。

### ステートマシンやテーマを使うdotlottie-android

.lottieのステートマシン（タップやホバーで状態が遷移する定義）やテーマ切り替えをそのまま使いたい場合は、LottieFilesが公開している[dotlottie-android](https://github.com/LottieFiles/dotlottie-android)が対象です。Rust製のdotlottie-rsで描画し、Compose向けに `DotLottieAnimation` を提供します。GitHubのリリースは0.17.0（2026年10月28日）ですが、READMEの導入例は0.5.0のまま更新されておらず、READMEが案内する座標 `com.lottiefiles:dotlottie-android` は2026年11月3日時点でMaven Centralに見つかりませんでした。JitPackのビルドAPIでは0.17.0が成功扱いになっているので、JitPackのリポジトリを追加して版番号を確認してから指定してください。

Android単体のアプリで、JSONか単一アニメーションの.lottieを再生するだけなら、Lottie Composeを使うのが妥当です。View版の `LottieAnimationView` と同じ `LottieDrawable` で描くため、既存のView実装から移行しても描画エンジンは変わりません。使う素材の表現が描画できるかは、どのライブラリでも実機で確かめてから採用してください。アニメーション形式そのものを選び直す段階であれば、[RiveとLottieの違いを比較｜料金・ランタイムサイズ・Unity対応・相互変換で選ぶ【2026年10月】](/tech/details/4461/)で、料金やランタイムサイズの観点から比較しています。アニメーションを含むAndroidアプリの設計から任せたい場合は、弊社の[Androidアプリ開発](/service/apps/android/)でご相談を受け付けています。

## よくある質問

### lottie-composeを追加するとき、lottie本体も書く必要はありますか？

不要です。`implementation("com.airbnb.android:lottie-compose:6.7.1")` の1行だけで本体側のクラスも参照できます。Lottieの版番号はCompose BOMとは別体系なので、BOMの版に合わせて選ぶ必要もありません。

### LottieAnimationで無限ループさせるにはどう書きますか？

`iterations = LottieConstants.IterateForever` を渡します。停止と再開を切り替えたい場合は `isPlaying` をステートで制御してください。既定では再開時に先頭へ巻き戻るため、途中から続けるなら `restartOnPlay = false` を併せて指定します。

### .lottieファイルはLottie Composeで再生できますか？

アニメーションが1本だけの.lottieなら、`RawRes` や `Asset` にそのまま渡して再生できます。`File` で渡す場合は拡張子で形式を判定するため、ファイル名を `.zip` にしてください。ステートマシンやテーマを含む.lottieを使うならdotlottie-androidを選びます。

### OSでアニメーションを減らす設定をしている利用者にはどう対応しますか？

JSONに `reduced motion` という名前のマーカーを置き、静止表示にします。6.7.1のComposeではマーカーを0フレーム目に置くと先頭の絵、それ以外に置くと最終フレームの絵で止まります。ローディング表示のように動きで処理中を伝えている画面は、静止した絵だけでは状態が伝わらないので、「読み込み中」のテキストやプログレス表示を併せて出します。`ignoreSystemAnimationsDisabled` は6.7.1では効かない引数なので、再生の強制には使えません。

### iOSやデスクトップでも同じコードを使えますか？

使えません。Lottie ComposeはAndroidライブラリで、Compose Multiplatformの共通コードからは参照できません。共通化するならCompottieを使い、使いたい表現が独自レンダラの対応表に含まれるかを先に確認します。

## 関連記事

- [Jetpack Composeとは？宣言的UIの仕組みとBOM運用・採用判断を解説](/tech/details/16030/)
- [Compose Multiplatformとは？KMPとの違いとiOS対応の現在地・採用判断を解説](/tech/details/16032/)
- [RiveとLottieの違いを比較｜料金・ランタイムサイズ・Unity対応・相互変換で選ぶ【2026年10月】](/tech/details/4461/)
- [Figma Motionとは？使い方・書き出し・無料でできる範囲を解説【2026年10月】](/tech/details/12722/)
- [Kotlin Coroutinesとは？suspendの仕組みと構造化並行性・採用判断](/tech/details/16036/)

---

出典: [Lottie Composeの使い方｜Jetpack Composeでの再生制御・dotLottie・状態遷移【6.7.1】](<https://www.issoh.co.jp/tech/details/4869/>)（株式会社一創）
