ミラティブのAndroidエンジニアのmorizoooです。
MirrativのAndroidアプリでは、新規で作る画面には積極的にJetpack Composeを活用しています。
Mirrativは、多くのユーザーがゲームや雑談などの配信をしており、配信を盛り上げるための演出として通常のアニメーションに加えてLottieも織り交ぜ、リッチなアニメーションを実装しています。
Jetpack Compose導入以前は、AnimationListenerのCallbackを入れ子にする形で複雑なアニメーションを実現していたのですが、いざJetpack Composeで実装しようとなるとどう実装するのが良いのか苦心しました。
今回の記事では、実際にアプリで使っているアニメーションを例に、Jetpack Composeで作ったアニメーションを紹介します。
Animatableを使ったアニメーション処理
Jetpack Composeのアニメーションについては、Googleの公式の記事がものすごくわかりやすいです。
MirrativのAndroidでは、Jetpack Composeでアニメーションを順次処理するためにAnimatableのsuspend functionであるanimateToを使うケースが多いです。 例えば以下のようにテキストを3つ順にフェードで出していくアニメーションをAnimatableで実現するには以下のようになります。
@Composable fun AnimatableSample() { val text1AlphaAnimatable = remember { Animatable(0f) } val text2AlphaAnimatable = remember { Animatable(0f) } val text3AlphaAnimatable = remember { Animatable(0f) } LaunchedEffect(true) { text1AlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 500) ) delay(500) text2AlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 500) ) delay(500) text3AlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 500) ) } Box( modifier = Modifier .fillMaxWidth() .fillMaxHeight(), contentAlignment = Alignment.Center ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( modifier = Modifier.alpha(text1AlphaAnimatable.value), text = "テキスト1" ) Spacer(modifier = Modifier.height(20.dp)) Text( modifier = Modifier.alpha(text2AlphaAnimatable.value), text = "テキスト2" ) Spacer(modifier = Modifier.height(20.dp)) Text( modifier = Modifier.alpha(text3AlphaAnimatable.value), text = "テキスト3" ) } } }
Lottie Animatableを組み合わせたアニメーション処理
Lottieは、AdobeのAfter Effectsで出力したjsonファイルを解析し、ネイティブでアニメーションをレンダリングするためのライブラリです。 LottieはJetpack Composeもサポートしていて、公式の解説もあります。
LottieにもLottie AnimatableというAnimatableのようなclassがあり、Lottie Animatableのsuspend functionのanimateを使って順次処理しています。 例えば以下のようなアニメーションを処理する場合には、AnimatableとLottie Animatableを組み合わせて実現しています。
- View全体を背景半透明の黒でフェードイン(Animatable)
- 宝箱を表示+開封(Animatable + Lottie Animatable)
- 2.のアニメーション開始から3000milliSec後に入手したアイテムをフェードイン
Animatable + Lottie Animatableで実現したコード
@Composable fun LottieAnimatableSample() { // 1. View全体を背景透過の黒でFadeInするためのAnimatable val containerAlphaAnimatable = remember { Animatable(0f) } // 2. 宝箱を表示するための開封Animatable + Lottie Animatable var isVisibleOpenTreasureAnimation by remember { mutableStateOf(false) } val openTreasureLottieComposition by rememberLottieComposition(LottieCompositionSpec.Asset(assetName = "open_tresure.json")) val openTreasureLottieAnimatable = rememberLottieAnimatable() // 3. 2.のアニメーション開始から3000milliSec後に入手したアイテムをFadeInするためのAnimatable val bonusItemAlphaAnimatable = remember { Animatable(0f) } LaunchedEffect(true) { containerAlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 500) ) launch { isVisibleOpenTreasureAnimation = true openTreasureLottieAnimatable.animate(composition = openTreasureLottieComposition) } delay(3000) bonusItemAlphaAnimatable.animateTo( targetValue = 1f, animationSpec = tween(durationMillis = 500) ) } Box( modifier = Modifier .fillMaxWidth() .fillMaxHeight() .alpha(containerAlphaAnimatable.value) .background(Color(0xCC000000)), contentAlignment = Alignment.Center, ) { if (isVisibleOpenTreasureAnimation) { LottieAnimation( composition = openTreasureLottieComposition, progress = openTreasureLottieAnimatable.progress, ) } // MirrativImage: httpsの画像を読み込むImageクラス MirrativImage( imageUrl = "入手したアイテムのURL", Modifier .alpha(bonusItemAlphaAnimatable.value) .width(128.dp) .height(128.dp) ) } }
FYI: AnimationListenerを使ったコード
参考までにJetpack Compose以前のAnimationListenerを使ったコードのサンプルを記載します。(※レイアウトのxmlは省略)
binding.root.doOnLayout { binding.rootContainer.startAnimation( AlphaAnimation(0f, 1f).apply { duration = 500 setAnimationListener(object : Animation.AnimationListener { override fun onAnimationEnd(animation: Animation) { binding.openTresureLottieAnimationView.visibility = View.VISIBLE launch { delay(3000) binding.bonusItemImageView.visibility = View.VISIBLE binding.bonusItemImageView.startAnimation(AlphaAnimation(0f, 1f).apply { duration = 500 }) } } override fun onAnimationEnd(animation: Animation) { } override fun onAnimationRepeat(animation: Animation) { } }) } ) }
今回は紹介のため、一部分のみ簡易な形で紹介しましたが、実際はもう少し複雑なアニメーションを作っています。 以下は冒頭のアニメーションと同じものになりますが、エンジニアがアニメーションを実装するためにデザイナーがFigmaで作ってくれた仕様書になります。
まとめ
複雑なアニメーションをJetpack Composeで実現した事例について紹介しました。 Mirrativでは、新規画面ではJetpack Composeでの実装を必須しています。
その中でリッチなアニメーションを実装することが多く、早めにJetpack Composeでのアニメーションの実装方針を作る必要がありました。 今回の実装がベストかどうかはわかりませんが、他社での事例があまりないことだったので、今回記事にしてみました。
少しでも参考になった方が居ますと幸いです。こうした方が良いよ、などあればコメントくださると嬉しいです!
We are hiring!
ミラティブでは一緒にアプリを作ってくれる Android エンジニアを募集中です!気軽にご連絡ください! www.mirrativ.co.jp