# Jetpack Compose: Advanced Animations Step by Step > Complete guide to advanced Compose animations: transitions, AnimatedVisibility, Animatable, gestures and performance for smooth Android interfaces. - Published: 2026-02-08 - Updated: 2026-03-31 - Author: SharpSkill - Tags: jetpack compose, android, animations, kotlin, ui - Reading time: 12 min --- Animations transform a functional application into a memorable user experience. Jetpack Compose offers a powerful declarative animation API that significantly simplifies the creation of fluid interfaces. This guide explores advanced techniques for building performant and maintainable animations. > **Prerequisites** > > This tutorial assumes familiarity with Compose basics (recomposition, state, modifiers). For fundamentals, check the Jetpack Compose interview questions guide first. ## Animation API Fundamentals in Compose Compose provides several API levels for animations. The choice depends on the desired level of control and animation complexity. The API divides into three main categories: high-level animations (`AnimatedVisibility`, `AnimatedContent`), state-based animations (`animate*AsState`), and low-level animations (`Animatable`, `Transition`). ```kotlin // AnimationLevels.kt // Overview of the three animation API levels @Composable fun AnimationApiOverview() { // High level: simple predefined animations AnimatedVisibility(visible = isVisible) { Text("Animated content") } // Intermediate level: state-driven animation val alpha by animateFloatAsState( targetValue = if (isSelected) 1f else 0.5f, label = "alpha" ) // Low level: full control over animation val animatable = remember { Animatable(0f) } LaunchedEffect(targetValue) { animatable.animateTo(targetValue) } } ``` Choosing the right API level is crucial for maintaining readable code while retaining the necessary flexibility. ## AnimatedVisibility: Elegant Enter and Exit Animations `AnimatedVisibility` is the ideal entry point for animating element appearances and disappearances. This API automatically handles composition and decomposition of content. The `enter` and `exit` parameters accept combinations of transitions that define the animation behavior. These transitions can be combined using the `+` operator. ```kotlin // AnimatedVisibilityExample.kt @Composable fun ExpandableCard( title: String, content: String, modifier: Modifier = Modifier ) { var isExpanded by remember { mutableStateOf(false) } Card( modifier = modifier.clickable { isExpanded = !isExpanded } ) { Column(modifier = Modifier.padding(16.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Text(text = title, style = MaterialTheme.typography.titleMedium) Icon( imageVector = if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore, contentDescription = null ) } // Expansion animation with fade + slide AnimatedVisibility( visible = isExpanded, enter = fadeIn(animationSpec = tween(300)) + expandVertically(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(200)) + shrinkVertically(animationSpec = tween(200)) ) { Text( text = content, modifier = Modifier.padding(top = 12.dp), style = MaterialTheme.typography.bodyMedium ) } } } } ``` Content inside `AnimatedVisibility` is only composed when `visible = true`, optimizing performance for lists with many expandable items. > **Combining Transitions** > > Available transitions include `fadeIn/fadeOut`, `slideIn/slideOut`, `expandIn/shrinkOut`, `scaleIn/scaleOut`. They can be freely combined to create custom effects. ## animate*AsState: State-Driven Animations The `animate*AsState` family of functions automatically animates changes to primitive values. This is the most idiomatic approach for simple animations in Compose. Each data type has its dedicated function: `animateColorAsState`, `animateFloatAsState`, `animateDpAsState`, `animateIntAsState`, etc. ```kotlin // AnimateAsStateExample.kt @Composable fun InteractiveButton( isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier ) { // Animated background color val backgroundColor by animateColorAsState( targetValue = if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant, animationSpec = tween(durationMillis = 250), label = "backgroundColor" ) // Animated elevation val elevation by animateDpAsState( targetValue = if (isSelected) 8.dp else 2.dp, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ), label = "elevation" ) // Animated text size val textSize by animateFloatAsState( targetValue = if (isSelected) 18f else 14f, label = "textSize" ) Surface( modifier = modifier.clickable(onClick = onClick), color = backgroundColor, shadowElevation = elevation, shape = RoundedCornerShape(12.dp) ) { Text( text = if (isSelected) "Selected" else "Select", modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp), fontSize = textSize.sp ) } } ``` The `animationSpec` parameter controls the timing behavior of the animation. The two most commonly used specs are `tween` (fixed duration with easing) and `spring` (realistic physics with bounce). ## Transition: Orchestrating Multiple Animations When multiple properties need to be animated in a coordinated manner, `updateTransition` provides centralized control. This API ensures all animations stay synchronized. The pattern involves defining an enum state, then creating animations for each property that depends on that state. ```kotlin // TransitionExample.kt // Card state: defines visual behavior enum class CardState { Collapsed, Expanded, Selected } @Composable fun AnimatedStateCard( cardState: CardState, modifier: Modifier = Modifier ) { // Central transition coordinating all animations val transition = updateTransition( targetState = cardState, label = "cardTransition" ) // Card height based on state val cardHeight by transition.animateDp( transitionSpec = { spring(stiffness = Spring.StiffnessLow) }, label = "height" ) { state -> when (state) { CardState.Collapsed -> 80.dp CardState.Expanded -> 200.dp CardState.Selected -> 160.dp } } // Border color based on state val borderColor by transition.animateColor( transitionSpec = { tween(300) }, label = "borderColor" ) { state -> when (state) { CardState.Collapsed -> Color.Transparent CardState.Expanded -> MaterialTheme.colorScheme.outline CardState.Selected -> MaterialTheme.colorScheme.primary } } // Corner radius based on state val cornerRadius by transition.animateDp( label = "cornerRadius" ) { state -> when (state) { CardState.Collapsed -> 8.dp CardState.Expanded -> 16.dp CardState.Selected -> 24.dp } } Card( modifier = modifier .height(cardHeight) .border(2.dp, borderColor, RoundedCornerShape(cornerRadius)), shape = RoundedCornerShape(cornerRadius) ) { // Card content } } ``` ## Animatable: Full Animation Control `Animatable` is the low-level API that provides complete programmatic control. This approach is necessary for interruptible animations, gestures, or complex scenarios. Unlike `animate*AsState`, `Animatable` allows stopping, reversing, or modifying an animation in progress without waiting for it to complete. ```kotlin // AnimatableExample.kt @Composable fun SwipeableCard( onDismiss: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit ) { // Horizontal offset controlled by Animatable val offsetX = remember { Animatable(0f) } val scope = rememberCoroutineScope() // Swipe threshold to trigger dismissal val dismissThreshold = 300f Box( modifier = modifier .offset { IntOffset(offsetX.value.roundToInt(), 0) } .pointerInput(Unit) { detectHorizontalDragGestures( onDragEnd = { scope.launch { if (abs(offsetX.value) > dismissThreshold) { // Animate out then callback val target = if (offsetX.value > 0) 1000f else -1000f offsetX.animateTo( targetValue = target, animationSpec = tween(200) ) onDismiss() } else { // Return to initial position with spring offsetX.animateTo( targetValue = 0f, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy ) ) } } }, onHorizontalDrag = { _, dragAmount -> scope.launch { // snapTo for instant finger tracking offsetX.snapTo(offsetX.value + dragAmount) } } ) } ) { content() } } ``` Key `Animatable` methods are `animateTo()` (animate to a target), `snapTo()` (instant change), and `stop()` (interruption). ## AnimatedContent: Content Transitions `AnimatedContent` animates transitions between different content. This API is perfect for state changes that completely modify the displayed UI. The `targetState` key determines when a transition should occur. The `transitionSpec` defines how outgoing and incoming content interact. ```kotlin // AnimatedContentExample.kt @Composable fun CounterWithAnimation( count: Int, modifier: Modifier = Modifier ) { AnimatedContent( targetState = count, modifier = modifier, transitionSpec = { // Determine animation direction val direction = if (targetState > initialState) { // New number enters from top slideInVertically { height -> -height } + fadeIn() togetherWith slideOutVertically { height -> height } + fadeOut() } else { // New number enters from bottom slideInVertically { height -> height } + fadeIn() togetherWith slideOutVertically { height -> -height } + fadeOut() } direction.using(SizeTransform(clip = false)) }, label = "counter" ) { targetCount -> Text( text = "$targetCount", style = MaterialTheme.typography.displayLarge, fontWeight = FontWeight.Bold ) } } ``` > **Performance with AnimatedContent** > > Content inside `AnimatedContent` is recomposed on each `targetState` change. For complex content, consider memoizing expensive elements or using an appropriate key strategy. ## Infinite Animations with rememberInfiniteTransition For looping animations (loading indicators, pulsing effects), `rememberInfiniteTransition` provides a dedicated API that requires no manual cycle management. ```kotlin // InfiniteTransitionExample.kt @Composable fun PulsingDot( color: Color = MaterialTheme.colorScheme.primary, modifier: Modifier = Modifier ) { val infiniteTransition = rememberInfiniteTransition(label = "pulse") // Looping scale animation val scale by infiniteTransition.animateFloat( initialValue = 0.8f, targetValue = 1.2f, animationSpec = infiniteRepeatable( animation = tween(600, easing = FastOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = "scale" ) // Synchronized opacity animation val alpha by infiniteTransition.animateFloat( initialValue = 0.5f, targetValue = 1f, animationSpec = infiniteRepeatable( animation = tween(600, easing = FastOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = "alpha" ) Box( modifier = modifier .size(24.dp) .scale(scale) .alpha(alpha) .background(color = color, shape = CircleShape) ) } ``` ## List Animations with LazyColumn List item animations require special attention. The `animateItem()` modifier (formerly `animateItemPlacement`) automatically animates reorderings. ```kotlin // ListAnimationExample.kt @Composable fun AnimatedTaskList( tasks: List, onToggle: (Task) -> Unit, onDelete: (Task) -> Unit, modifier: Modifier = Modifier ) { LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp) ) { items( items = tasks, key = { it.id } // Stable key required for animateItem ) { task -> var isVisible by remember { mutableStateOf(true) } // Exit animation before deletion AnimatedVisibility( visible = isVisible, exit = shrinkVertically() + fadeOut() ) { TaskItem( task = task, onToggle = { onToggle(task) }, onDelete = { isVisible = false // Delay to let animation complete }, modifier = Modifier.animateItem( fadeInSpec = tween(300), fadeOutSpec = tween(300), placementSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ) ) ) } // Trigger deletion after animation LaunchedEffect(isVisible) { if (!isVisible) { delay(300) onDelete(task) } } } } } ``` > **Stable Keys for List Animations** > > Without a stable `key` parameter, `animateItem` cannot track items between recompositions. Use a unique ID rather than the list index. ## Canvas Animations For custom visual effects, combining `Canvas` with animated values offers total flexibility. ```kotlin // CanvasAnimationExample.kt @Composable fun AnimatedProgressRing( progress: Float, // 0f to 1f modifier: Modifier = Modifier ) { // Progress animation with spring for natural feel val animatedProgress by animateFloatAsState( targetValue = progress, animationSpec = spring( dampingRatio = Spring.DampingRatioLowBouncy, stiffness = Spring.StiffnessVeryLow ), label = "progress" ) // Continuous rotation animation val infiniteTransition = rememberInfiniteTransition(label = "rotation") val rotation by infiniteTransition.animateFloat( initialValue = 0f, targetValue = 360f, animationSpec = infiniteRepeatable( animation = tween(2000, easing = LinearEasing) ), label = "rotation" ) val primaryColor = MaterialTheme.colorScheme.primary val trackColor = MaterialTheme.colorScheme.surfaceVariant Canvas( modifier = modifier .size(120.dp) .rotate(rotation) ) { val strokeWidth = 12.dp.toPx() val radius = (size.minDimension - strokeWidth) / 2 // Background circle (track) drawCircle( color = trackColor, radius = radius, style = Stroke(width = strokeWidth, cap = StrokeCap.Round) ) // Animated progress arc drawArc( color = primaryColor, startAngle = -90f, sweepAngle = animatedProgress * 360f, useCenter = false, style = Stroke(width = strokeWidth, cap = StrokeCap.Round), topLeft = Offset(strokeWidth / 2, strokeWidth / 2), size = Size(radius * 2, radius * 2) ) } } ``` ## Animation Performance Optimization Poorly optimized animations can cause jank and drain battery. Here are best practices for maintaining 60 FPS. The first rule is to avoid allocations during animation. Use `graphicsLayer` rather than modifiers that trigger recompositions. ```kotlin // PerformanceOptimization.kt @Composable fun OptimizedAnimatedCard( isExpanded: Boolean, modifier: Modifier = Modifier ) { val scale by animateFloatAsState( targetValue = if (isExpanded) 1.1f else 1f, label = "scale" ) val alpha by animateFloatAsState( targetValue = if (isExpanded) 1f else 0.8f, label = "alpha" ) Card( modifier = modifier // ✅ graphicsLayer: GPU modifications without recomposition .graphicsLayer { scaleX = scale scaleY = scale this.alpha = alpha } // ❌ Avoid: .scale(scale).alpha(alpha) // These modifiers trigger recompositions ) { Text("Card content") } } // Example with remembered lambda to avoid allocations @Composable fun OptimizedClickableItem( onClick: () -> Unit, content: @Composable () -> Unit ) { // ✅ Stable remembered lambda val interactionSource = remember { MutableInteractionSource() } Box( modifier = Modifier .clickable( interactionSource = interactionSource, indication = ripple(), onClick = onClick ) ) { content() } } ``` The second critical point concerns animations in lists. Limit the number of simultaneous animations and use `derivedStateOf` for derived calculations. ```kotlin // ListPerformance.kt @Composable fun PerformantAnimatedList( items: List, modifier: Modifier = Modifier ) { // Calculate once whether the list is empty val isEmpty by remember { derivedStateOf { items.isEmpty() } } LazyColumn(modifier = modifier) { items( items = items, key = { it.id } ) { item -> // Lightweight animation only on initial appearance var hasAppeared by remember { mutableStateOf(false) } LaunchedEffect(Unit) { hasAppeared = true } val alpha by animateFloatAsState( targetValue = if (hasAppeared) 1f else 0f, animationSpec = tween(200), label = "itemAlpha" ) ItemCard( item = item, modifier = Modifier.graphicsLayer { this.alpha = alpha } ) } } } ``` ## Conclusion Animations in Jetpack Compose offer a balance between ease of use and advanced control. Here are the key takeaways: - ✅ Choose the right API level based on complexity (AnimatedVisibility → animate*AsState → Animatable) - ✅ Use `updateTransition` to coordinate multiple related animations - ✅ Prefer `spring` for natural animations and `tween` for precise durations - ✅ Always provide a stable `key` parameter for list animations - ✅ Optimize with `graphicsLayer` to avoid unnecessary recompositions - ✅ Test animations on real devices to validate performance Mastering Compose animations distinguishes professional Android applications. These techniques, combined with attention to performance, enable the creation of fluid and engaging user experiences. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/android/jetpack-compose-advanced-animations