Kmp animations
Skill almasumdev/awesome-kotlin-multiplatform-agent-skills/.github/skills/ui/kmp-animations
Curated agent skills, conventions, and workflows for building Kotlin Multiplatform (KMP) apps with AI coding agents.
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill kmp-animationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Animation APIs in Compose Multiplatform (animate*AsState, updateTransition, AnimatedContent, Animatable) and the performance caveats that differ between Android, iOS, and Desktop. Use when adding motion to shared UI.
SKILL.md
4.2 KB, as published. Nobody here has run it
KMP Animations
Instructions
Compose Multiplatform ships the full Jetpack Compose animation API. Code is identical across targets; runtime cost is not.
1. Value-based animations
@Composable
fun LikeButton(isLiked: Boolean, onClick: () -> Unit) {
val scale by animateFloatAsState(
targetValue = if (isLiked) 1.2f else 1f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "like-scale",
)
IconButton(onClick = onClick, modifier = Modifier.graphicsLayer { scaleX = scale; scaleY = scale }) {
Icon(if (isLiked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, contentDescription = null)
}
}
2. Transitions for multiple coordinated values
@Composable
fun Chip(selected: Boolean, text: String) {
val t = updateTransition(selected, label = "chip")
val bg by t.animateColor(label = "bg") { if (it) Color(0xFF6750A4) else Color(0xFFE7E0EC) }
val fg by t.animateColor(label = "fg") { if (it) Color.White else Color(0xFF1D1B20) }
Box(Modifier.background(bg, RoundedCornerShape(8.dp)).padding(horizontal = 12.dp, vertical = 6.dp)) {
Text(text, color = fg)
}
}
3. Enter/exit content
AnimatedContent(
targetState = screen,
transitionSpec = {
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it } + fadeOut())
},
label = "screen-swap",
) { current ->
when (current) {
Screen.Home -> HomeUi()
Screen.Detail -> DetailUi()
}
}
4. Gesture-driven — Animatable
val offset = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Modifier.pointerInput(Unit) {
detectHorizontalDragGestures(
onHorizontalDrag = { _, dx ->
scope.launch { offset.snapTo(offset.value + dx) }
},
onDragEnd = {
scope.launch { offset.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow)) }
},
)
}.graphicsLayer { translationX = offset.value }
Animatable is the only animation API that supports cancellable physics-based animations driven by gestures — always prefer it over animateFloatAsState for drag-to-dismiss.
5. Cross-target performance notes
- iOS: Compose renders through Skia on Metal. Very large, frequently-changing lists combined with per-item animations can hit the frame budget harder than on Android. Keep item-level animations cheap (opacity / translation) and use
Modifier.graphicsLayer— it forces a separate layer that compositing can reuse. - Desktop (JVM): animations render on a software / OpenGL canvas depending on the platform. On Linux without a compositor they can stutter; keep animations opt-in behind
LocalDensity.current.fontScaleor a settings flag. - wasmJs: every animation frame incurs a JS <-> wasm bridge cost. Prefer CSS-free Compose animations over mixing with HTML.
6. Avoid re-triggering animations
Wrap the changing parameter in remember { derivedStateOf { ... } } so only real changes drive animate*AsState. Do not call animateFloatAsState inside a forEach over a mutable list without stable keys — each item recomposition restarts the animation.
7. Honouring reduced motion
// commonMain
expect val reducedMotionEnabled: Flow<Boolean>
@Composable
fun motionDuration(default: Int): Int {
val reduced by reducedMotionEnabled.collectAsState(initial = false)
return if (reduced) 0 else default
}
On iOS, bridge to UIAccessibilityIsReduceMotionEnabled(); on Android to Settings.Global.ANIMATOR_DURATION_SCALE.
Checklist
- Used
animate*AsState/updateTransitionfor simple target-driven animations. - Used
Animatablefor gesture-driven motion. -
graphicsLayerapplied to elements that animate transform/alpha. - Every animation respects a reduced-motion preference flow.
- No unkeyed list items animate on recomposition.
- Profiled on iOS and Android (iOS is the frame-budget risk).