4
1

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 1 year has passed since last update.

JetpackComposeを利用して状態に寄らないアニメーションを実装する

Last updated at Posted at 2022-12-26

こんにちは!
Life is Tech! 名古屋土曜日スクールでクラスマネージャーをしているカーキです。
本日、アドベントカレンダーの9日目を担当します。
普段はAndroidエンジニアとして、働いているのでAndroidに関するネタを投稿します。

概要

2年前にもAndroidでポヨンするアニメーションを実装する紹介をしたのですが、今回はその2022年版として、JetpackComposeだとどう実装をするのかを想定して紹介していきます。

具体的には、以下のプリン画像をタップして、ポヨヨンとアニメーションする動きを実装していきます。
purin.png

ゴール

今回のゴールとしては、2年前に投稿したAndroidでポヨンするアニメーションを実装するで発見した、最もポヨンらしさを表現できるパラメータをそのまま利用してJetpackComposeで同じ動きを目指していきます。

poyon.gif
👆こんな感じ

当時はViewscaleという大きさの比率を示すプロパティをアニメーションを用いて変更させることでポヨンを実現していました。

ぽよよん.jpeg

アニメーションの時間経過とその挙動を示したのが上の図です。
タップされた直後にスケールが0.6となり、バウンスをしながら元のスケールの値である1.0に戻っていきます。

またバウンスのアニメーションにはAndroidxのdynamicanimationにあるSpringアニメーションを利用していました。Springアニメーションではその名の通りばねの物理法則をアニメーションに利用したアニメーション方法になります。詳細は当時の記事を読んでもらえばと思いますが、理想的なポヨンを実現するパラメータとして以下の値を利用していました。

dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
stiffness = SpringForce.STIFFNESS_LOW

表示する

まずはJetpackComposeでプリンを表現できなければ、何も始まりません。
Imageのコンポーネントを利用して表示していきます。

Poyon.kt
@Preview
@Composable
fun Poyon() {
    Image(
        modifier = Modifier
            .size(40.dp)
            .scale(1.0f),
        painter = painterResource(id = R.drawable.sweets_purin),
        contentDescription = null
    )
}

プレビュー結果

これでプリンが表示できました。
すぐにプレビュー結果を見ることができるので、安心感がありますね😀

アニメーションを考える

ここからどうポヨンのアニメーションを実装していくのかを考えていきます。

JetpackComposeでアニメーションを実装していく上で、公式のドキュメントがとても参考になるのでみてみましょう。

スクリーンショット 2022-12-27 0.16.06.png

このようなアニメーションを実装する際に何を使用すると良いのかがわかるフローチャートが載っています。
今回のポヨンに関してはViewの状態変化によらないアニメーションになるので、Animatableが該当します。

JetpackComposeのアニメーションでは高レベルアニメーションとして状態変化に応じたアニメーションを実装するAPIが多く用意されており、ドキュメントも充実しています。今回はAnimatableという低レベルアニメーションを利用するため、自身でCoroutineScopeを用意する必要があります。高レベルアニメーションを駆使することで実現は可能ですが、無駄な状態変化を生み出してしまうことになるので、Animatableで実装をするのが良いと思います。

まず小さくする

Animatableでは、変化対象の値をAnimatableでラップして利用します。
そしてCoroutineScopeの内部でアニメーションによる値の変化を加えていきます。

Poyon.kt
val coroutineScope = rememberCoroutineScope()
val scale = remember {
    Animatable(1f)
}

上記で指定したスケールをプリンに適応させつつ、タップ時にはまず0.6のスケールになって欲しいのでそのアニメーションを加えてみます。

Image(
    modifier = Modifier
        .size(40.dp)
        .scale(scale.value)
        .clickable(
            onClick = {
                coroutineScope.launch {
                    scale.animateTo(
                        0.6f,
                        animationSpec = tween(durationMillis = 0)
                    )
                }
            }
        ),
    painter = painterResource(id = R.drawable.sweets_purin),
    contentDescription = null
)

最初に0.6のスケールとなるようtweenアニメーションを追加しています。
tweenでは指定した遅延時間の中でEasingしながらアニメーションするものになりますが、遅延時間を0msecと指定しているため、一瞬でスケールが0.6となります。

このアニメーションの結果がこちらです。
小さくなるプリン.gif

タップした瞬間にスケールが0.6になっているのが確認できます。

大きさを戻す

小さくするところまでできたので、次は大きさをバウンスさせながら戻していきたいです。
同じようにAnimatableを使って実装していきます。

アニメーションの動きの詳細であるAnimationSpecにはspringアニメーションが用意されているのでそれを利用していきます。
Image全体の実装が以下のようになります。

Image(
    modifier = Modifier
        .size(40.dp)
        .scale(scale.value)
        .clickable(
            onClick = {
                coroutineScope.launch {
                    scale.animateTo(
                        0.6f,
                        animationSpec = tween(durationMillis = 0)
                    )
                    scale.animateTo(
                        1f,
                        animationSpec = spring(
                            dampingRatio = Spring.DampingRatioMediumBouncy,
                            stiffness = Spring.StiffnessLow,
                        )
                    )
                }
            }
        ),
    painter = painterResource(id = R.drawable.sweets_purin),
    contentDescription = null
)

JetpackComposeとAndroidViewであっても、同じAndroidの世界なのでSpringアニメーションの指定の方法が同じなのは助かりますね。

スプリング.gif

アニメーションはこんな感じで、しっかりポヨンしています🍮

今回のコンポーネント全体のコードは以下のようになります。

Poyon.kt
@Preview
@Composable
fun Poyon() {

    val coroutineScope = rememberCoroutineScope()
    val scale = remember {
        Animatable(1f)
    }

    Image(
        modifier = Modifier
            .size(40.dp)
            .scale(scale.value)
            .clickable(
                onClick = {
                    coroutineScope.launch {
                        scale.animateTo(
                            0.6f,
                            animationSpec = tween(durationMillis = 0)
                        )
                        scale.animateTo(
                            1f,
                            animationSpec = spring(
                                dampingRatio = Spring.DampingRatioMediumBouncy,
                                stiffness = Spring.StiffnessLow,
                            )
                        )
                    }
                }
            ),
        painter = painterResource(id = R.drawable.sweets_purin),
        contentDescription = null
    )
}

最後に

JetpackComposeのアニメーションは様々な種類が事前に用意されており便利ですが、状態変化によらないアニメーションとなるとどうするんだ?と内心思っていました。
それでもAnimatableを利用することでサクッと実装できてしまって感動しました。
やっぱりJetpackCompose良いですね

余談ですが、プリンの手話可愛いですよね
それは、皆さんも楽しいポヨンライフを🍮

参考

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?