見出し画像

JetpackComposeで「思い出アルバム」ストーリーを作ってみた - REALITY Advent Calendar 2022 #14

こんにちは!REALITY Androidチームのいしいです。
REALITY Advent Calendar 2022の14日目担当になります。

今回は、Spotifyまとめ2022に影響を受けて、思い出アルバムの機能をストーリー風にアレンジしてみた話です。

「思い出アルバム」とは?

自分のREALITY活動の振り返りができる機能になります。

実装を行ったIKEPさんの記事↓

期間限定で公開されていた本機能は、Webページとして実装されており、AndroidやiOS側では表示のみ対応していました。
今回はボタンで表示の操作までできるよう、Android側で実装してみました。

今回作ったもの

こちら↓


Spotifyまとめ同様、左タップで初めから再生したり、画像をSNSに共有する機能が付いています。

実装

メインとなる部分は、REALITY Androidアプリでも採用されているJetpack Composeという開発ツールキットを使用しています。

JetpackComposeでは複雑なアニメーションを簡単に実装できるAPIをいくつか提供しており、必要に応じて使い分けることができます。(詳しくはこちら

今回はKotlin CoroutineAnimatableを組み合わせて実装しました。

実装方法はAnimatableのドキュメントに沿って行っています。

val color = remember { Animatable(Color.Gray) }
LaunchedEffect(ok) {
    color.animateTo(if (ok) Color.Green else Color.Red)
}
Box(Modifier.fillMaxSize().background(color.value))

上記の例では、Animatable のインスタンスを初期値 Color.Gray で作成および記憶しています。ブール値フラグ ok の値に応じて、Color.Green または Color.Red へのアニメーションが行われます。その後ブール値を変更すると、他の色へのアニメーションが開始されます。値が変更されたときに進行中のアニメーションが存在した場合、アニメーションはキャンセルされ、新しいアニメーションが、現在のスナップショットの値から、現在の速度で開始されます。

https://developer.android.com/jetpack/compose/animation?hl=ja#animatable

この例では色のパラメーターでしたが座標や図形のアルファ値など色々と応用できます。

最初の画面で表示されていた波紋のようなアニメーションを例にしてみます。

@Composable
fun CircleAnimation() {
    val circleRadius = remember { Animatable(0f) }
    val circleStroke = remember { Animatable(0f) }
    val alpha = remember { Animatable(1f) }

    Canvas(modifier = Modifier.fillMaxSize()) {
        val size = size
        drawCircle(
            center = Offset(size.width / 2, size.height / 2),
            radius = circleRadius.value,
            color = Color.White,
            style = Stroke(width = circleStroke.value),
            alpha = alpha.value
        )
    }

    val circleRadiusLast = 800f
    val animationSec = 600

    LaunchedEffect(Unit) {
        launch {
            circleStroke.animateTo(
                targetValue = circleRadiusLast,
                animationSpec = tween(animationSec)
            )
        }
        launch {
            delay(200)
            circleRadius.animateTo(
                targetValue = circleRadiusLast,
                animationSpec = tween(animationSec)
            )
        }
        launch {
            delay(300)
            circleStroke.animateTo(
                targetValue = 0f,
                animationSpec = tween(animationSec, easing = LinearOutSlowInEasing)
            )
            alpha.animateTo(targetValue = 0f, animationSpec = tween(100))
        }
    }
}

この例では、円の半径と線の太さ、円の不透明度の3つのパラメータが並列に動いています。

コードもシンプルで書きやすい印象です。

今後の展望

本機能は個人的に作ったもので、ユーザーさんに公開できるかはまだわかりません。公開ができない場合でも、他の箇所で今回の実装経験を活かしてユーザーさんが楽しめる機能を提供できると嬉しいです。

最後に

明日のアドベントカレンダーは、グレートルイさんです。グレート!