6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Jetpack compose でカードが回転するLayoutを作ってみる

Last updated at Posted at 2023-03-01

タイトル通りです。某バーコード決済アプリのTop画面にあるカードレイアウトのイメージです。
Composeで思いのほか楽に再現できたので投下

回転するカード

コード

Preview使えばすぐに見れます。

サクッと説明

Modifier.graphicsLayer で縦への移動、回転
Modifier.zIndex で Z軸の入れ替え

これだけで雑に作れました。
cameraDistanceはデフォルトだと、回転する際に画像が大きくなりすぎるので設定してます。

Box(
    modifier = Modifier
        .graphicsLayer(
            translationY = translationY,
            rotationX = rotationX,
            cameraDistance = cameraDistance,
        )
        .background(Color.White)
        .size(width = 360.dp, height = 280.dp)
        .zIndex(cardZIndex)
        .clickable { onCardTap() }
) {
    Text(text = "Card")
}

アニメーション値は以下にて制御
全部単一のFloat値なので animate*asStateのanimateFloatAsState を使ってます。

animationSpecは全部tweenです。 
ほぼ単純に値をしていするだけでできてしまってますね。

enum class CardZIndex(val value: Float) {
    BACK(-1f), FRONT(1f);
}
fun CardZIndex.switch() : CardZIndex {
    return when(this) {
        CardZIndex.BACK -> CardZIndex.FRONT
        CardZIndex.FRONT -> CardZIndex.BACK
    }
}

enum class TranslationY(val value : Float) {
    Default(0f), Top(-300f)
}
fun TranslationY.transit(): TranslationY {
    return when(this) {
        TranslationY.Default -> TranslationY.Top
        TranslationY.Top -> TranslationY.Default
    }
}

enum class RotationX(val value: Float) {
    Front(0f), Back(-180f)
}
fun RotationX.rotate(): RotationX {
    return when(this) {
        RotationX.Front -> RotationX.Back
        RotationX.Back -> RotationX.Front
    }
}

const val DURATION_MS = 1000

@Composable
fun PayPayCardLayout() {
    var zIndex by remember { mutableStateOf(CardZIndex.BACK) }
    var translationY by remember { mutableStateOf(TranslationY.Default) }
    var rotationX by remember { mutableStateOf(RotationX.Front) }

    val animateZIndex by animateFloatAsState(
        targetValue = zIndex.value,
        animationSpec = tween(
            durationMillis = DURATION_MS,
            easing = LinearEasing
        )
    )
    val animateTransitionY by animateFloatAsState(
        targetValue = translationY.value,
        animationSpec = repeatable(
            iterations = 1,
            animation = tween(
                durationMillis = DURATION_MS/2,
                easing = LinearEasing
            ),
            repeatMode = RepeatMode.Reverse
        )
//        keyframes { // TODO keyframesでもうちょいましなやりかたがありそう
//            durationMillis = DURATION_MS
//            TranslationY.Top.value to (DURATION_MS/2) // アニメーション時間の真ん中でTopに移動
//        }
    )
    val animateRotationX by animateFloatAsState(
        targetValue = rotationX.value,
        animationSpec = tween(
            durationMillis = DURATION_MS,
            easing = LinearEasing
        )
    )

    val scope = rememberCoroutineScope()

    PayPayCard(
        cardZIndex = animateZIndex,
        translationY = animateTransitionY,
        rotationX = animateRotationX,
        onCardTap = {
            zIndex = zIndex.switch()
            rotationX = rotationX.rotate()
            translationY = translationY.transit()
            scope.launch {// translationYをリバースしてもどす
                delay((DURATION_MS/2).toLong())
                translationY = translationY.transit()
            }
        }
    )
}

translationYのリバースアニメーションを無理やり戻しているので、ここはもっといい書き方がありそう。

おわり

graphicsLayerはいかがとても参考になりました。業務系なら大概にアニメーションは簡単にできそうですね。

Have Fun With Jetpack Compose GraphicsLayer Modifier

composeすごい。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?