経緯
現在アプリでいろんなViewをComposeに置き換え中です。
その中で、アニメーションで子要素の表示が切り替わるCrossFadeView
という内製のカスタムViewGroupがありました。そしてComposeにはもともとCrossfadeが備わっています。
ただ、内製のCrossFadeView
は実際はクロスフェードせず、前のViewが完全にフェードアウトしてから次のViewがフェードインするという、"嘘CrossFade" だったのです……なんとまあ……。1
クロスフェード | 嘘クロスフェード2 |
---|---|
そうなると、正解の動きをするComposeのCrossfadeは使えませんね(トホホ)
カスタムのAnimetionで作ろう編
実現したいのは 表示→fadeout→fadein→表示→fadeout→fadein… という風に時間経過で表示が切り替わっていくアニメーションです。
今回のような要件を満たすカスタムのAnimationを作るにあたり、大きく分けてAnimatable
と、AnimatedContentを使う方法の2通りを比較しました。
特にAnimatedContentはやりたいことがそのまま簡単に実現できそうでしたが、少々怖い注意書きが…。
ので、Animatableを使って実装することにしました。
早速ですがこんな感じ。
@Composable
fun MyCrossFadeIndexed(
size: Int,
content: @Composable (Int) -> Unit,
) {
var index by remember { mutableStateOf(0) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(key1 = alpha) {
while (true) {
delay(4000)
alpha.animateTo(targetValue = 0f,animationSpec = tween(durationMillis = 300))
index = (index + 1) % size
alpha.animateTo(targetValue = 1f,animationSpec = tween(durationMillis = 300))
}
}
Box(modifier = Modifier.alpha(alpha.value)) {
content(index)
}
}
こちらがアニメーションの本体です。
var index by remember { mutableStateOf(0) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(key1 = alpha) {
while (true) {
delay(4000)
alpha.animateTo(targetValue = 0f,animationSpec = tween(durationMillis = 300))
index = (index + 1) % size
alpha.animateTo(targetValue = 1f,animationSpec = tween(durationMillis = 300))
}
}
AnimatableのalphaをBoxのModifier.alphaに入れて、
4秒表示したら0.3秒かけてfadeout、完全に消えたタイミングで表示するViewを切り替えて次のViewをまた0.3秒かけてfadeinさせるという方法になります。
(contentはindexを受け取って、何を表示させるかを選択する想定です)
余談
このあとコードレビューで「Composableの可変長引数(vararg)を引数に取ればいいんじゃない?」とのご指摘が……かっこよすぎる。
-
実はこのCustomViewGroupは自分が作っている。 ↩
-
fade through というらしい ↩