Lottie Animation でユーザーの操作をトリガーにアニメーションを開始し終了を検出する(iOS, Android)

蓼科山山頂から360度のパノラマ

こんにちは、 id:numanuma08 です。5月になって天気の良い日も多くて山が捗りますね。Airbnbの作っているアニメーションフレームワークLottieを使ってユーザーがボタンを押したときにアニメーションを開始、アニメーションが終わったらコールバックを呼び出す処理を実現したくなったので、そのサンプルコートです。公式ドキュメントを読んでもいまいち掴みどころがなくてちょっと調べることになりました。

iOS(SwiftUI)、Android(Jetpack Compose)を対象としています。

実現したいこと

  • ユーザーがボタンを押すとアニメーションが再生される
  • アニメーションが終了したらコールバックメソッドが呼び出される

リソースの準備

Lottieはアニメーショをいくつかのファイル形式で定義できますが、今回はlottie形式のバイナリを使いました。各プラットフォームごとのファイルの取り扱い方法を説明します。

iOS

Assets Bundleにファイルを格納します。Assets Bundleに適当な名前をつけてアニメーション定義ファイルを登録します。もし必要なら、デバイスやテーマ別などでアニメーション定義ファイルを変更できます。

Assets Bundle にファイルを登録する

Android

res/rawディレクトリにファイルを格納します。こちらも必要ならデバイスの画面サイズやテーマ、言語などでアニメーションを変更できます。

rawディレクトリに格納する

アニメーション用のコンポーネントをレイアウトする

画面の必要な場所にアニメーション用のコンポーネントを配置します。このときリソースの読み込みを行います。

iOS

LottieViewを使います。DotLottieFile.assetを使ってアニメーション定義ファイルを読み込んでアニメーションを再生します。

LottieView {
        try await DotLottieFile.asset(named: "CheckmarkAnimation")
}

Android

LottieAnimationを使います。rememberLottieCompositionでアニメーション定義ファイルを読み込み、Floatを返すラムダ式をパラメータに渡します。

val checkmarkAnimation by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.checkmark2))
LottieAnimation(
  composition = checkmarkAnimation,
  progress = { 0f}
)

アニメーションを制御する

「ボタンを押したらアニメーションを1度だけ再生する」を実現するため、アニメーションの制御をします。

iOS

LottiePlaybackModeをstateで定義しておいてボタンを押したときに値を変化させます。

@State var checkmarkAnimationPlaybackMode: LottiePlaybackMode = .paused(at: .progress(0))

Button(
    action: {
         // アニメーションを最初から最後まで1度だけ再生する
        checkmarkAnimationPlaybackMode = .playing(.fromProgress(0.0, toProgress: 1, loopMode: .playOnce))
    },
    label: {
        Text("I am a button")
    }
)

LottieView {
    try await DotLottieFile.asset(named: "CheckmarkAnimation")
}
.playbackMode(checkmarkAnimationPlaybackMode)

Android

LottieAnimatableを生成してLaunchedEffectなどで状態の変更をトリガーに値を変更し、アニメーションを行います。

var isChecked by remember { mutableStateOf(false) }
val checkmarkAnimation by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.checkmark2))
val checkmarkAnimatable = rememberLottieAnimatable()
LaunchedEffect(isChecked) {
    if (isChecked) {
        // animateはsuspend関数。アニメーションを最初から最後まで1度だけ再生する
        checkmarkAnimatable.animate(
            checkmarkAnimation,
            clipSpec = LottieClipSpec.Progress()
        )
    }
}

Button(
    modifier = Modifier.fillMaxWidth(),
    onClick = {
        isChecked = true
    }
) {
    Text(text = "I am a button")
}

LottieAnimation(
    composition = checkmarkAnimation,
    // animatableのprogressを使って再生位置の描画をする
    progress = { checkmarkAnimatable.progress },
)

アニメーションの終了を検知する

アニメーションの終わりをトリガーに処理をしたいので、終了を検知しなければなりません。それぞれのプラットフォームの方法で行います。

iOS

animationDidFinishモディファイアを使います。このコールバックが呼ばれたらアニメーションの終了です。

LottieView {
    try await DotLottieFile.asset(named: "CheckmarkAnimation")
}
.playbackMode(checkmarkAnimationPlaybackMode)
.animationDidFinish { _ in
  // アニメーション終了時の処理を行う
}

Android

LottieAnimatable.animateはsuspend関数なので、関数の実行が終わったらアニメーションが終わった扱いとなります。

LaunchedEffect(isChecked) {
    if (isChecked) {
        // animateはsuspend関数。アニメーションを最初から最後まで1度だけ再生する
        checkmarkAnimatable.animate(
            checkmarkAnimation,
            clipSpec = LottieClipSpec.Progress()
        )
        // アニメーション終了時の処理を行う
    }
}

まとめ

Lottie Animationを使ってユーザーの操作をトリガーにアニメーションを開始し、終了を検出するiOSとAndroidの方法をまとめました。公式リファレンスを見ても説明が断片的で、ソースコードやサンプルコードから情報収集が必要だったので忘備録的な記録です。同じことをやろうと思っている人の役に立ったら幸いです。

各種公式リファレンスへのリンクはコチラ

  • Android(Jetpack Compose) airbnb.io

  • iOS(SwiftUI, UIKit)

airbnb.io

あと、はてなブログに動画を載せるのが地味に面倒でした。Google Driveに配置したファイルの埋め込み用共有リンクを使っています。