Compose animations
Skill almasumdev/awesome-kotlin-android-agent-skills/.github/skills/ui/compose-animations
Curated agent skills, conventions, and workflows for building Kotlin Android apps with AI coding agents.
npx -y skills add almasumdev/awesome-kotlin-android-agent-skills --skill compose-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
Expert guidance on Compose animation APIs including animate*AsState, AnimatedVisibility, updateTransition, AnimatedContent, and shared element transitions introduced in Compose 1.7+. Use this for motion design tasks.
SKILL.md
5.0 KB, as published. Nobody here has run it
Jetpack Compose Animations
Instructions
Compose ships a layered animation toolkit. Pick the lowest-friction API that fits the motion.
1. Value Animations — animate*AsState
val elevation by animateDpAsState(
targetValue = if (pressed) 1.dp else 6.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "cardElevation",
)
val tint by animateColorAsState(
targetValue = if (isFav) Color.Red else MaterialTheme.colorScheme.outline,
label = "favTint",
)
Use animate*AsState when a single property changes between two targets. Always pass label — it shows up in the Animation Preview in Android Studio.
2. Enter / Exit — AnimatedVisibility
AnimatedVisibility(
visible = state.showBanner,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Banner(state.bannerText)
}
Combine specs with +. Prefer semantic specs (fadeIn, slideInVertically) over hand-crafted tweens.
3. Between-Content — AnimatedContent
AnimatedContent(
targetState = state,
label = "screen",
transitionSpec = {
(slideInHorizontally { it } + fadeIn()) togetherWith
(slideOutHorizontally { -it } + fadeOut())
},
) { s ->
when (s) {
is ArticlesUiState.Loading -> LoadingIndicator()
is ArticlesUiState.Error -> ErrorView(s.message)
is ArticlesUiState.Loaded -> ArticleList(s.articles)
}
}
Always use SizeTransform (set via sizeTransform = SizeTransform(clip = false)) if the target content has a different size.
4. Coordinated — updateTransition
For multiple properties that should animate from the same state change:
val transition = updateTransition(targetState = expanded, label = "card")
val radius by transition.animateDp(label = "r") { if (it) 24.dp else 8.dp }
val alpha by transition.animateFloat(label = "a") { if (it) 1f else 0.6f }
val height by transition.animateDp(label = "h") { if (it) 240.dp else 80.dp }
5. Infinite Animations
val infinite = rememberInfiniteTransition(label = "pulse")
val scale by infinite.animateFloat(
initialValue = 1f, targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(600, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse,
),
label = "scale",
)
Box(Modifier.scale(scale).size(24.dp).background(Color.Red, CircleShape))
6. Shared Element Transitions (Compose 1.7+)
SharedTransitionLayout {
NavHost(navController, startDestination = ListRoute) {
composable<ListRoute> { ArticleList(onOpen = { id -> navController.navigate(DetailRoute(id)) }, sharedScope = this@SharedTransitionLayout, animatedScope = this) }
composable<DetailRoute> { args -> ArticleDetail(id = args.toRoute<DetailRoute>().id, sharedScope = this@SharedTransitionLayout, animatedScope = this) }
}
}
@Composable
fun ArticleThumb(id: String, scope: SharedTransitionScope, animatedScope: AnimatedVisibilityScope) = with(scope) {
AsyncImage(
model = coverUrl,
contentDescription = null,
modifier = Modifier.sharedElement(
state = rememberSharedContentState(key = "cover-$id"),
animatedVisibilityScope = animatedScope,
).size(96.dp).clip(MaterialTheme.shapes.small),
)
}
Use matching keys on both source and destination. Keep payloads identical (same aspect ratio, same clip).
7. Gestures + Physics
val offsetX = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Modifier.pointerInput(Unit) {
detectHorizontalDragGestures(
onHorizontalDrag = { _, dx -> scope.launch { offsetX.snapTo(offsetX.value + dx) } },
onDragEnd = { scope.launch { offsetX.animateTo(0f, spring(stiffness = Spring.StiffnessMedium)) } },
)
}
Animatable is the right tool when gestures and animation share state.
8. Performance Notes
- Prefer
graphicsLayer { alpha = a; scaleX = s }overModifier.alpha(a).scale(s)when animating — single layer, no recomposition. - Animate on
Int/Dp/Float, notModifierswaps. - Respect reduced motion: read
AccessibilityManager.isReducedMotionEnabled(viaLocalAccessibilityManager) and fall back to instantsnap.
Checklist
- Every animation has a descriptive
labelfor the Animation Preview. -
AnimatedVisibilityis used for show/hide,AnimatedContentfor content swaps. - Multi-property animations use a single
updateTransition. - Shared element transitions key source and destination with the same string.
-
graphicsLayeris preferred over stacking visual modifiers for animated values. - Reduced-motion setting is respected for non-essential motion.