入室歓迎!Welcome機能のプロトタイプを作ってみた REALITY Advent Calendar 2022 #22
本記事はREALITY Advent Calendar 2022の2022/12/22分になります。
おはこんハロチャオ〜
REALITY Androidチームのエンジニアリングマネージャーやってるメタルおじさんです。
今回の開発合宿では、iOSエンジニアのションロー・サーバーエンジニアのabe・Androidエンジニアのメタルおじさん の3人でチームWelcomeを結成し、「Welcome機能」というライブ配信に入室してくれた視聴者を歓迎する機能のプロトタイプを作りました。
※ この機能は正式なものではなく、実際のアプリにはリリースされておりません。リリースされるかどうかは未定です。もしリリースされるとしても最終的な機能仕様はこの記事で紹介したものとは異なる可能性があります。
Welcome機能とは?
配信をしていて新しい人が入室してきた時、その人に対してワンタップで歓迎の気持ちを伝えられる機能です。
配信者の画面はこんな感じになります。
配信者の画面に、「Welcome」と書かれた謎のボタンが表れていました。これを押すとどうなるのでしょうか?
入室した視聴者の方から見ると、このようなことが起こっていました。
※ 合宿時に録画したスクリーンキャプチャデータがiOSのものしか残っていなかったのでiOSのものを掲載しましたが、実際にはAndroid側にも同様のUIを実装しました。
この「歓迎しています!」のダイアログは、同時に配信を見ている他の視聴者には表示されません。配信を観に行った時に配信者の方から「ようこそ!」と言ってもらえると、なんだか仲良くなれそうな気がしますね!
Newcomer機能
上記のスクリーンショットのコメント欄で「◯◯さんが入室しました」という文字の隣に「初見」「5回目」といった文字が出ているのに気づきましたか?これはWelcome機能と一緒に開発したNewcomer機能です。
Welcomeするためには、誰が初見さんなのかがわかると嬉しいです。そのため、入室時に初見さんかどうかがわかるようにしてみました!
※ Newcomer機能も正式なものではなく、実際のアプリにはリリースされておりません。リリースされるかどうかは未定です。もしリリースされるとしても最終的な機能仕様はこの記事で紹介したものとは異なる可能性があります。
Jetpack Composeによるアニメーション実装
ここからは実装の話になります。
といってもiOS,Android,Server全部でやったことを書くと分量が多すぎるので、私の担当だったAndroidのアニメーションの実装に絞って書きます。
今回の画面はJetpack ComposeのAnimatedContentを使用して実装しました。AnimatedContentを使用すると、ある状態から別の状態にUIが変化する様子を自動的にアニメーション表示することができます。
Welcome機能の視聴者側の表示は以下のような順番で行われます。
UIの状態としては
1. 表示しない状態
2. 最初のビュー(Welcomeのスタンプ画像とメッセージ)
3. 二番目のビュー(フォローするボタン)
の3種類があり、1→2→3→1の順に状態遷移をします。
今回のこだわりポイントは、最初のビューと二番目のビューの登場アニメーション・消えるアニメーションをそれぞれ別にしたことです。このようなアニメーションも、AnimatedContentを使えばシンプルに実装することができます。先程のWelcomeされた人の画面のコードは、だいたいこんな感じになっています。
sealed interface WelcomeUiState {
data class FirstView(
// 最初のビューに必要なデータ
) : WelcomeUiState
data class SecondView(
// 二番目に出すビューに必要なデータ
) : WelcomeUiState
}
AnimatedContent<WelcomeUiState?>(
targetState = uiState,
transitionSpec = {
when {
// 1 最初のビューは中央から拡大で登場
initialState == null && targetState is FirstView -> {
scaleIn(animationSpec = tween(durationMillis = 500, easing = EaseOutBack)) with fadeOut()
}
// 2 最初のビューが小さくなって消えて、二番目のビュー下から出てくる
initialState is FirstView && targetState is SecondView -> {
slideInVertically(
animationSpec = tween(500, delayMillis = 700)
) { it } with scaleOut(
animationSpec = tween(durationMillis = 500, easing = EaseInBack)
)
}
// 3 二番目のViewが下に消える
initialState is SecondView && targetState == null -> {
fadeIn() with slideOutVertically(
animationSpec = tween(500)
) { it } + fadeOut()
}
else -> {
// 今の仕様だとここには来ないはずだが、何かしらの指定がないといけないので適当に
fadeIn() with fadeOut()
}
}
},
) { uiState ->
when (uiState) {
is FirstView -> {
FirstView(
uiState = uiState
modifier = Modifier.fillMaxSize(),
)
}
is SecondView -> {
SecondView(
uiState = uiState,
modifier = Modifier.fillMaxSize(),
)
}
null -> {
// 最初のビュー・二番目のビューと同じ大きさの空白を表示しないとscaleOutがおかしくなるので
Spacer(modifier = Modifier.fillMaxSize())
}
}
}
非表示・最初のビュー・二番目のビューという三つの状態はAnimatedContentのtargetStateに渡す値で表現されています。そして、これらがどう遷移した時にどうアニメーションするのかをtransitionSpecに指定します。
transitionSpecの中でinitialStateとtargetStateを見ることで、状態がどこからどこに変化しているかによってアニメーションの仕方を変えることができます。アニメーションの中身についても柔軟に指定でき、遷移にかかる時間やイージングカーブを指定したり、複数のアニメーションを組み合わせたり、さらに個々のアニメーションに別々の遅延を付けたり……といった細かな指定もできる表現力も持っています。
※ AnimatedContentは本記事執筆時点(Compose 1.3)ではまだExperimental扱いなので、APIは将来変更になる可能性があります。
おわりに
Jetpack Composeを使うと、イイ感じのアニメーションがシンプルなコードで表現できることがわかりました。Jetpack Composeには他にもアニメーションを実装するための様々な便利な機能がたくさんあります。今後もたくさん使っていきたいなと思いました。
REALITYではまだViewベースの実装をしている古い画面がたくさん残っていますが、新しい画面についてはJetpack Composeの導入を積極的に行っています。古い画面についてもできる範囲で徐々にJetpack Composeへの移行を進めており、より気持ちよく使えるUIを目指しています。
明日は
明日のアドベントカレンダーはiOSエンジニア ヤザキさん の「REALITYでLive Activitiesを試してみた」です。お楽しみに!