こんにちは!
Life is Tech! 名古屋土曜日スクールでクラスマネージャーをしているカーキです。
本日、アドベントカレンダーの9日目を担当します。
普段はAndroidエンジニアとして、働いているのでAndroidに関するネタを投稿します。
概要
2年前にもAndroidでポヨンするアニメーションを実装する紹介をしたのですが、今回はその2022年版として、JetpackComposeだとどう実装をするのかを想定して紹介していきます。
具体的には、以下のプリン画像をタップして、ポヨヨンとアニメーションする動きを実装していきます。
ゴール
今回のゴールとしては、2年前に投稿したAndroidでポヨンするアニメーションを実装するで発見した、最もポヨンらしさを表現できるパラメータをそのまま利用してJetpackComposeで同じ動きを目指していきます。
当時はView
のscaleという大きさの比率を示すプロパティをアニメーションを用いて変更させることでポヨンを実現していました。
アニメーションの時間経過とその挙動を示したのが上の図です。
タップされた直後にスケールが0.6
となり、バウンスをしながら元のスケールの値である1.0
に戻っていきます。
またバウンスのアニメーションにはAndroidxのdynamicanimation
にあるSpringアニメーションを利用していました。Springアニメーションではその名の通りばねの物理法則をアニメーションに利用したアニメーション方法になります。詳細は当時の記事を読んでもらえばと思いますが、理想的なポヨンを実現するパラメータとして以下の値を利用していました。
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
stiffness = SpringForce.STIFFNESS_LOW
表示する
まずはJetpackComposeでプリンを表現できなければ、何も始まりません。
Image
のコンポーネントを利用して表示していきます。
@Preview
@Composable
fun Poyon() {
Image(
modifier = Modifier
.size(40.dp)
.scale(1.0f),
painter = painterResource(id = R.drawable.sweets_purin),
contentDescription = null
)
}
これでプリンが表示できました。
すぐにプレビュー結果を見ることができるので、安心感がありますね😀
アニメーションを考える
ここからどうポヨンのアニメーションを実装していくのかを考えていきます。
JetpackComposeでアニメーションを実装していく上で、公式のドキュメントがとても参考になるのでみてみましょう。
このようなアニメーションを実装する際に何を使用すると良いのかがわかるフローチャートが載っています。
今回のポヨンに関してはViewの状態変化によらないアニメーションになるので、Animatable
が該当します。
JetpackComposeのアニメーションでは高レベルアニメーションとして状態変化に応じたアニメーションを実装するAPIが多く用意されており、ドキュメントも充実しています。今回はAnimatable
という低レベルアニメーションを利用するため、自身でCoroutineScopeを用意する必要があります。高レベルアニメーションを駆使することで実現は可能ですが、無駄な状態変化を生み出してしまうことになるので、Animatable
で実装をするのが良いと思います。
まず小さくする
Animatable
では、変化対象の値をAnimatable
でラップして利用します。
そしてCoroutineScopeの内部でアニメーションによる値の変化を加えていきます。
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
となります。
タップした瞬間にスケールが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アニメーションの指定の方法が同じなのは助かりますね。
アニメーションはこんな感じで、しっかりポヨンしています🍮
今回のコンポーネント全体のコードは以下のようになります。
@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良いですね
余談ですが、プリンの手話可愛いですよね
それは、皆さんも楽しいポヨンライフを🍮
参考
-
Android Developer Jetpack Compose アニメーション
-
Android Button onClick Animation Using Jetpack Compose