タイトル通りです。某バーコード決済アプリの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すごい。