What You Will Build
A wallet-style card stack where credit cards fan out with a spring animation when tapped, and collapse back into a tight stack. Each card has a slight rotation in the collapsed state and spreads to equal spacing when expanded.
Why This Pattern Matters
Card stack interactions are used in payment apps (Apple Wallet, Google Pay) and card selectors. This teaches animated offset and rotation with spring physics, which creates a tactile, satisfying feel.
Step 1: Define Card Data
@Composable
fun WalletAnimationScreen() {
var expanded by remember { mutableStateOf(false) }
val cards = listOf(
Color(0xFF1A1A2E) to "**** 4532",
Color(0xFF4ECDC4) to "**** 8901",
Color(0xFFFF6B6B) to "**** 3456",
Color(0xFF45B7D1) to "**** 7890"
)
// ...
}
Step 2: Animate Offset and Rotation per Card
Each card gets its own animateDpAsState for vertical offset and animateFloatAsState for rotation. The index determines how far each card moves:
cards.forEachIndexed { i, (color, number) ->
val offsetY by animateDpAsState(
targetValue = if (expanded) (i * 120).dp else (i * 30).dp,
animationSpec = spring(dampingRatio = 0.7f),
label = "card$i"
)
val rotation by animateFloatAsState(
targetValue = if (expanded) 0f else i * 3f,
animationSpec = spring(),
label = "rot$i"
)
Box(
Modifier
.fillMaxWidth().height(100.dp)
.offset(y = offsetY)
.graphicsLayer(rotationZ = rotation)
.clip(RoundedCornerShape(16.dp))
.background(color)
.padding(16.dp)
) {
Column {
Text("Credit Card",
color = Color.White.copy(alpha = 0.7f),
style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.weight(1f))
Text(number, color = Color.White,
style = MaterialTheme.typography.titleMedium)
}
}
}
Step 3: Toggle on Tap
Box(
Modifier
.fillMaxWidth()
.clickable { expanded = !expanded },
contentAlignment = Alignment.TopCenter
) {
// cards rendered here
}
Tips and Pitfalls
- dampingRatio = 0.7f gives a gentle overshoot. Try 0.4f for a more playful bounce.
- Collapsed rotation (i * 3f) creates the fanned-out deck look. Without it, collapsed cards just look stacked.
- Z-ordering: Cards are drawn in list order, so the last card appears on top. Reverse the list if you want card 0 on top.
- Haptic feedback: Add
HapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)on tap for a polished feel.