agentsclimarketplace

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.

Install
npx -y skills add almasumdev/awesome-kotlin-multiplatform-agent-skills --skill kmp-animations

Assembled 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.fontScale or 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/updateTransition for simple target-driven animations.
  • Used Animatable for gesture-driven motion.
  • graphicsLayer applied 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).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.