kotlin

Lottie Composeの使い方|Jetpack Composeでのアニメーション実装と再生制御

Lottie ComposeはAirbnbが公開しているLottieのJetpack Compose向けAPIで、After Effectsから書き出したJSONアニメーションをコンポーザブルとして描画します。この記事では、Gradleへの追加から再生制御、色の差し替え、パフォーマンス設定までを、GitHubで公開されている実装(バージョン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日公開)です。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 を追加すれば LottieCompositionLottieProperty といった本体側のクラスもそのまま参照できます。両方を明示的に書いているサンプルを見かけますが、バージョンがずれる事故を増やすだけなので不要です。

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

ビルド設定上の対応範囲は minSdk 21compileSdk 36targetSdk 36 です。Android 5.0以降であれば動作対象に入ります。

バージョン 公開日 主な変更
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+)

6.7.1のあと、2026年7月時点まで約9か月にわたって新しいリリースは出ていません。破壊的変更の少ない成熟したライブラリなので、バージョンを固定して運用しても当面の実害はありません。なお公式ドキュメントの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)
}

Composeのバージョンを揃える必要はありません。Lottie ComposeはCompose BOMに含まれず、独自のバージョン体系で公開されています。ライブラリ自体はBOMを固定してビルドされていますが、利用側でCompose Compilerに合わせて追随させる作業は発生しません。

アニメーション読み込みと表示の最小実装

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 で、無限ループではなく「事実上終わらない回数」の指定です。途中で止めたい場合は後述の再生制御を併用します。

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 を受け取る版:進捗を呼び出し側が管理する。ジェスチャーやダウンロード進捗に連動させる場合はこれ。
  • isPlayingiterations を直接受け取る版:内部で駆動まで面倒を見る。単純な再生だけならこれで足ります。

進捗がラムダである点には理由があります。値で受け取ると毎フレームの進捗変化がコンポーズ関数の再実行を招きますが、ラムダなら読み取りが描画フェーズまで遅延され、再コンポーズを回避できます。Float 版が非推奨になったのもこのためです。

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

戻り値の LottieCompositionResultState<LottieComposition?> を継承しつつ isLoadingisFailureisSuccesserror を公開しています。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 を組み合わせます。

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

animateLottieCompositionAsStateの主要な引数

再生の細かい制御は animateLottieCompositionAsState が担います。戻り値の LottieAnimationStateState<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 })

reverseOnRepeattrue にすると、繰り返しのたびに逆再生されて往復します。restartOnPlay の既定は true で、isPlayingfalse から 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へ正規化して返します。ここで animateLottieCompositionAsState を併用しないでください。時間駆動と操作駆動が同じ進捗を奪い合い、指を離した瞬間に飛ぶ動きになります。

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

「AをX回再生したあとBへ切り替える」といった逐次的な制御には rememberLottieAnimatable を使います。animate は suspend 関数なので、完了を待ってから次の処理へ進めます。

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.compositionLottieAnimationState.progress に依拠せよ」と明記しています。

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

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 という名前の塗り」を対象にします。プロパティは型が分かれており、COLORSTROKE_COLORInteger(ARGB)、COLOR_FILTERColorFilterGRADIENT_COLORInteger[] です。LottieDynamicProperties は渡された値の型を見て振り分けるため、型が合わないと反映されないまま何のエラーも出ないことがあります。

キーパスに使う名前はAfter Effects側のレイヤー名です。デザイナーがレイヤー名を変えると静かに壊れるので、差し替え対象のレイヤーは命名を固定する取り決めを先に作っておくと安全です。デザイン工程の作り方はFigma Motionとは|2026年新搭載タイムラインでUIアニメーションを作る方法で扱っています。

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

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

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

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

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

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

asyncUpdatesAUTOMATICENABLEDDISABLED の3値を取り、有効時は全ノードの進捗計算(setProgress)をメインスレッドの外へ逃がします。

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

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

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

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

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

コードは正しいのにアニメーションが動かない、あるいは一瞬で終わる場合、まず疑うのは端末設定です。animateLottieCompositionAsState の内部では、実際の速度が次のように計算されています。

// 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側でアニメーションを減らす設定が有効な利用者に対しては、再生を強行するのではなく静止表示へ落とすのが正しい設計です。Lottieにはそのための仕組みが入っています。

JSONに「reduced motion」という名前のマーカーを置いておくと、アニメーションが無効化された環境では、そのマーカーの開始フレームで静止した状態が描画されます。LottieAnimation の描画処理が、animationsEnabledfalse かつ該当マーカーが存在する場合に、渡された進捗ではなくマーカーの開始フレームを使うためです。マーカー名は大文字小文字を区別せず、reduced motionreduced_motionreduced-motionreducedmotion の4通りが認識されます。

なお LottieAnimatable.animate にある ignoreSystemAnimationsDisabled は、名前は似ていますが別の引数です。OSのアニメーション低減設定そのものを無視して再生を強制するもので、スケールによる速度の除算とは無関係です。混同したまま指定しても、前項の症状は解消しません。

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

ビットマップを含むアニメーションでは、JSONと同じ場所に画像を置いただけでは読み込まれません。rememberLottieCompositionimageAssetsFolder は既定値が null で、指定しない限り画像の読み込み処理自体がスキップされます。assets配下のフォルダ名を明示的に渡してください。

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

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

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

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

Compose Multiplatformで同じJSONを使う場合の選択肢

Lottie ComposeはAndroid専用です。lottie-compose のビルド設定は com.android.library プラグインで構成されており、Kotlin Multiplatformのターゲットを持たないため、androidMain 以外から参照できません。JSONファイル自体はプラットフォーム非依存なので、iOSはlottie-ios、Webはlottie-webという分担であれば成立します。

共通コードで完結させたい場合の選択肢がCompottieです。プラットフォームの実装に委譲せず、Kotlinで書かれた独自レンダラで描画するライブラリで、Android・iOS・JVM・macOS・Webに対応します。最新は2.2.4(2026年6月24日)で、表現機能を削って容量を抑えた compottie-lite、dotLottie対応の compottie-dot などモジュールが分かれています。独自レンダラである以上、After Effectsの全機能が再現される保証はありません。対応表がリポジトリで公開されているので、使いたい表現が含まれるかを先に確認してください。

Android単体のアプリなら、素直にLottie Composeを使うのが妥当です。Airbnbが本体のレンダラを維持しており、After Effectsの機能カバー率で優位に立ちます。アニメーション形式そのものを選び直す段階であれば、RiveとLottieの違いを徹底比較|ファイルサイズ・インタラクティブ性・Unity対応で選ぶで、ファイルサイズやインタラクティブ性の観点から比較しています。

よくある質問

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

不要です。implementation("com.airbnb.android:lottie-compose:6.7.1") の1行だけで本体側のクラスも参照できます。

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

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

Jetpack ComposeやCompose Compilerのバージョンを合わせる必要はありますか

ありません。Compose BOMに含まれておらず独自のバージョン体系で公開されているため、Compose側を更新してもLottieを追随させる作業は発生しません。

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

JSONに reduced motion という名前のマーカーを置きます。アニメーションが無効化された環境では、そのマーカーの開始フレームで静止した状態が描画されます。ignoreSystemAnimatorScaleignoreSystemAnimationsDisabled で再生を強制する方法もありますが、利用者の設定を無視することになるため、ローディング表示のようにアニメーションが機能そのものである画面に限るべきです。

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

Lottie ComposeはAndroidライブラリなので、Compose Multiplatformの共通コードからは参照できません。共通コードで完結させたい場合はCompottieが選択肢になりますが、独自レンダラのためAfter Effectsの対応機能に差があります。

関連記事

資料請求

RELATED POSTS 関連記事