👍

KMP Jetpack Compose で animation

2024/05/16に公開

LazyList で並べた Card にアニメーションを入れる。
Card 上に配置されたボタンをタップすると、アニメーション実行。

出来上がり画面

AnimatedVisibility を使います

https://developer.android.com/develop/ui/compose/animation/composables-modifiers

の記載通り

コード

カード全体

  • ボタンのところ onClick で visible のステートを変更する
  • LaunchedEffect で enter -> exit するように調整。(これがないと1回タップすると enter 部分、もう1回タップすると exit 部分になってしまう)
@Composable
fun CardItemView(
    item: FakeCardItem,
    cardHeight: Int = 80,
) {
    var visible by remember { mutableStateOf(false) }

    LaunchedEffect(visible) {
        // 一定時間後に非表示にする
        if (visible) {
            delay(1000)
            visible = false
        }
    }

    OutlinedCard {
        Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier.fillMaxWidth()
        ) {
            Box(
                modifier = Modifier
                    .weight(1f, fill = true)
                    .height(cardHeight.dp)
            ) {
                CardMainBox(item)
                AnimatedBox(visible)
            }
            FilledTonalButton(
                onClick = { visible = !visible },
                shape = RoundedCornerShape(8.dp),
                modifier = Modifier.size(cardHeight.dp),
            ) {
                Icon(
                    Icons.Default.ThumbUp,
                    contentDescription = "done"
                )
            }
        }
    }
}

アニメーションして入り込んでくる部分

  • 配置してあるボタン側からアニメーションが入り込んでくるようにしたかったので enter, exit を少し調整
@OptIn(ExperimentalResourceApi::class)
@Composable
private fun AnimatedBox(
    visible: Boolean,
) {
    AnimatedVisibility(
        visible = visible,
        enter = slideInHorizontally (
            initialOffsetX = {400},
        ),
        exit = shrinkHorizontally (
            shrinkTowards = Alignment.End,
        )
    ) {
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize()
                .background(Color.Yellow)
        ) {
            Image(
                painter = painterResource(Res.drawable.compose_multiplatform),
                contentDescription = "dummy image",
            )
        }
    }
}

KMP と書いてあるが、Res.drawable のあたりくらいしか KMP 用の部分はない。

元々表示されてるカード部分(特に面白いところはない)

@Composable
private fun CardMainBox(
    item: FakeCardItem
) {
    Column(
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.SpaceAround,
        modifier = Modifier.fillMaxSize()
            .padding(horizontal = 16.dp, vertical = 8.dp),
    ) {
        Text(
            item.title,
            maxLines = 1
        )
        Text(
            item.summary,
            maxLines = 2,
            overflow = TextOverflow.Ellipsis,
        )
    }
}

つぶやき

アニメーションを一定時間後にOFFする方法がよくわからなくてつまづいた。
LaunchedEffect で一定時間待って state 戻せば良いだけ。。と気づくまで結構時間がかかってしまった。

あと最初は Jetpack Compose で Android 用の view のみで書いてたので
Image 部分に Lottie を使って可愛いアニメーション付きでやってたのだが
サンプルコードを public repository に載せとこうと思った段階で
欲を出して iOS でも動くように Compose Multiplatform にしたら
Lottie compose は Android 専用です との記載を見つけてやむなく単なる Image にした。
残念・・

まだ Compose Multiplatform どこまで使うか難しいとこですね。

sample repository

https://github.com/maripiyoko/kmp-sample/

参考

Animation on Jetpack compose

https://developer.android.com/develop/ui/compose/animation/quick-guide

https://developer.android.com/quick-guides/content/video/animation-in-compose

ハートレイルズ

Discussion