11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Android: Lottie Animation を任意の位置とサイズにオーバーレイ(ViewOverlay)表示させる

Last updated at Posted at 2021-02-20

2021/12/06:編集メモ:Lottie 3.4.0 では以下の方法でうまく動作していますが、 Lottie 3.6.0 ではうまく動作していないかもしれません。Lottie 3.6.0 以降ではこの記事に記載した方針と ViewGroupOverlay + View (foreground = LottieDrawable) の組み合わせでうまく動かせました。今後あらためて検証する時間が取れたら情報を整理し、 ViewGroupOverlay での対応を記載予定です。

TL;DR

  • Lottie Animation を ViewOverlay に表示することで、View Tree に影響を与えずにアニメーション表示させる
  • ViewOverlay は Android 4.3 以上で使えます

やりたいことと問題点

Button タップを起点として Lottie でリッチなアニメーションを表示させるとき、レイアウト上の Button のサイズよりも広い範囲にアニメーションを表示させたいことがあります。

たとえば、 Twitter Android アプリのお気に入り Button UI はタップするとハートマークよりも広い範囲にアニメーションが表示されています。

heart1.png

heart2.png

これを XML レイアウト上で表現すると以下のようなレイアウトとなりますが、この方法では Button の周囲の View と LottieAnimationView が被ることがあったり、見た目上の margin と XML での margin 指定がズレており、レイアウトの配置に苦労します。レイアウトの重なり順にも問題があるため、LottieAnimationView を最前面に移動させる工夫も必要になります。

このレイアウトで A も C もアニメーションを付けることになったら XML が大変なことになりそうです...

image.png

アニメーションを XML で表現したときの構成:

<LinearLayout...>
    <Button.../>
    <FrameLayout...
        android:layout_marginTop="-14dp"
        android:layout_marginStart="-10dp"
        android:layout_marginEnd="-10dp">
        <Button.../>
        <com.airbnb.lottie.LottieAnimationView.../>
    </FrameLayout>
    <Button..../>
</LinearLayout>

解決策: Lottie Animation を ViewOverlay へ表示する

Android 4.3 以上であれば、ViewOverlay を利用できます。

ViewOverlay は View の最前面に存在しているレイヤーで、任意の View か Drawable を ViewOverlay に表示することができます。
ViewOverlay は View ごとに存在します。ViewOverlay は描画のみに使われるため、タップイベントなどには反応しません。

Activity や Fragment 画面遷移時の Shared Element Transition も ViewOverlay により実現されています。

ViewOverlay の使い方

  • 任意の View に対して view.overlay でアクセスできる
  • オーバーレイ表示するタイミングで view.overlay.add(Drawable) または、 view.overlay.add(View) により、描画したい要素を追加する
    • Drawable の描画位置は drawable.bounds で指定する
  • オーバーレイ表示が不要になったら、view.overlay.remove() で要素を削除する

ViewOverlay + LottieDrawable 実装

後述の PositionedLottieDrawable を用いて、以下のように実装します。

// アニメーションの起点となる Button
private val button: ToggleButton = ...
// オーバーレイ表示したい View。 Button の Parent である場合が多い
private val targetView: ViewGroup = (button.parent as! ViewGroup)
private val drawable = PositionedLottieDrawable()
private val compositionTask: LottieTask<LottieComposition>
init {
    drawable.addAnimatorListener(object: Animator.AnimatorListener {
        override fun onAnimationStart(animation: Animator?) = Unit
        override fun onAnimationEnd(animation: Animator?) {
            // アニメーション終了で Drawable を Overlay から削除
            targetView.overlay.remove(drawable)
            // アニメーション終了後の状態 (Toggle ON/OFF) で表示
            button.alpha = 1f
        }
        override fun onAnimationCancel(animation: Animator?) = Unit
        override fun onAnimationRepeat(animation: Animator?) = Unit
    })
    compositionTask = LottieCompositionFactory
        .fromRawRes(context, R.raw.lottie_animation)
        .addListener {
            // 非同期で Lottie JSON を読み込み、drawable へ設定する
            drawable.composition = it
        }
}
fun clickButton(checked: Boolean) {
    if (checked) {
        // checked = true へ移行するアニメーション処理

        // より安全な実装とするには、ここで drawable.composition が読み込み済みであることを確認してください
        // 読み込みが完了していなければ compositionTask の終了を待ってからアニメーションを実行する必要があります

        // アニメーション中は Button を非表示
        // 表示したままでよければ alpha を変更する必要はない
        // アニメーション中にも Button タップ判定を拾いたいため、visibility ではなく alpha 変更
        button.alpha = 0f
        // アニメーション Drawable の座標を計算する
        // このサンプルでは Button とアニメーションが中央合わせとなるように計算している
        drawable.x = (button.x - (drawable.composition.bounds.width() - button.width) / 2)
        drawable.y = (button.y - (drawable.composition.bounds.height() - button.height) / 2)
        targetView.overlay.add(drawable)
        drawable.playAnimation()
    } else {
        // checked = false へ移行する実装は省略
        // button.alpha やアニメーションの処理を実装する
        // こちらにもアニメーションが必要なら、checked = true のアニメーションを停止してから
        // あたらしくアニメーションを開始したりする
    }
}

PositionedLottieDrawable ワークアラウンド

Lottie 3.6.1 時点で、LottieDrawable は bounds で指定した座標に描画してくれない問題があります。

以下の PositionedLottieDrawable により、描画座標を修正します。

class PositionedLottieDrawable : LottieDrawable() {
    var x: Float = 0f
    var y: Float = 0f
    override fun draw(canvas: Canvas) {
        canvas.save()
        canvas.translate(x, y)
        super.draw(canvas)
        canvas.restore()
    }
}

ViewOverlay + LottieAnimationView ではうまく動かない

Lottie 3.6.1 時点で、LottieAnimationView は ViewOverlay に追加しても正しくアニメーションしません。

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?