# Jetpack Compose: Fortgeschrittene Animationen Schritt für Schritt > Vollständiger Leitfaden zu fortgeschrittenen Compose-Animationen: Übergänge, AnimatedVisibility, Animatable, Gesten und Performance für flüssige Android-Oberflächen. - Published: 2026-02-08 - Updated: 2026-04-28 - Author: SharpSkill - Tags: jetpack compose, android, animations, kotlin, ui - Reading time: 12 min --- Animationen verwandeln eine funktionale App in ein einprägsames Nutzererlebnis. Jetpack Compose bietet eine leistungsstarke deklarative Animations-API, die das Erstellen flüssiger Oberflächen erheblich vereinfacht. Dieser Leitfaden behandelt fortgeschrittene Techniken zum Aufbau performanter und wartbarer Animationen. > **Voraussetzungen** > > Dieses Tutorial setzt Vertrautheit mit den Compose-Grundlagen voraus (Recomposition, State, Modifiers). Für die Grundlagen empfiehlt es sich, zuerst den Leitfaden zu Jetpack-Compose-Interviewfragen zu konsultieren. ## Grundlagen der Animations-API in Compose Compose stellt mehrere API-Ebenen für Animationen bereit. Die Wahl hängt vom gewünschten Maß an Kontrolle und der Komplexität der Animation ab. Die API gliedert sich in drei Hauptkategorien: High-Level-Animationen (`AnimatedVisibility`, `AnimatedContent`), zustandsbasierte Animationen (`animate*AsState`) und Low-Level-Animationen (`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) } } ``` Die Wahl der passenden API-Ebene ist entscheidend, um lesbaren Code zu erhalten und gleichzeitig die nötige Flexibilität zu bewahren. ## AnimatedVisibility: Elegante Eintritts- und Austrittsanimationen `AnimatedVisibility` ist der ideale Einstiegspunkt, um das Erscheinen und Verschwinden von Elementen zu animieren. Diese API verwaltet automatisch das Komponieren und Dekomponieren des Inhalts. Die Parameter `enter` und `exit` akzeptieren Kombinationen von Übergängen, die das Animationsverhalten festlegen. Diese Übergänge lassen sich mit dem Operator `+` kombinieren. ```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 ) } } } } ``` Der Inhalt innerhalb von `AnimatedVisibility` wird nur komponiert, wenn `visible = true` gilt, was die Performance bei Listen mit vielen ausklappbaren Elementen optimiert. > **Übergänge kombinieren** > > Verfügbare Übergänge sind unter anderem `fadeIn/fadeOut`, `slideIn/slideOut`, `expandIn/shrinkOut`, `scaleIn/scaleOut`. Sie lassen sich frei kombinieren, um eigene Effekte zu erstellen. ## animate*AsState: Zustandsbasierte Animationen Die Funktionsfamilie `animate*AsState` animiert Änderungen primitiver Werte automatisch. Das ist der idiomatischste Ansatz für einfache Animationen in Compose. Jeder Datentyp besitzt eine eigene Funktion: `animateColorAsState`, `animateFloatAsState`, `animateDpAsState`, `animateIntAsState` usw. ```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 ) } } ``` Der Parameter `animationSpec` steuert das zeitliche Verhalten der Animation. Die beiden gebräuchlichsten Specs sind `tween` (feste Dauer mit Easing) und `spring` (realistische Physik mit Federung). ## Transition: Mehrere Animationen orchestrieren Wenn mehrere Eigenschaften koordiniert animiert werden müssen, bietet `updateTransition` eine zentrale Steuerung. Diese API stellt sicher, dass alle Animationen synchron bleiben. Das Muster besteht darin, einen Enum-Zustand zu definieren und dann Animationen für jede Eigenschaft zu erstellen, die von diesem Zustand abhängt. ```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: Volle Kontrolle über die Animation `Animatable` ist die Low-Level-API, die vollständige programmatische Kontrolle bietet. Dieser Ansatz wird für unterbrechbare Animationen, Gesten oder komplexe Szenarien benötigt. Im Gegensatz zu `animate*AsState` erlaubt `Animatable` das Anhalten, Umkehren oder Modifizieren einer laufenden Animation, ohne auf deren Ende zu warten. ```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() } } ``` Die zentralen Methoden von `Animatable` sind `animateTo()` (zum Ziel animieren), `snapTo()` (sofortige Änderung) und `stop()` (Unterbrechung). ## AnimatedContent: Inhaltsübergänge `AnimatedContent` animiert Übergänge zwischen unterschiedlichen Inhalten. Diese API eignet sich perfekt für Zustandsänderungen, die die angezeigte Oberfläche vollständig verändern. Der Schlüssel `targetState` bestimmt, wann ein Übergang stattfinden soll. `transitionSpec` definiert, wie ausgehende und eingehende Inhalte interagieren. ```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 bei AnimatedContent** > > Der Inhalt innerhalb von `AnimatedContent` wird bei jeder Änderung von `targetState` neu komponiert. Bei komplexen Inhalten lohnt es sich, aufwendige Elemente zu memoisieren oder eine geeignete Key-Strategie einzusetzen. ## Endlosanimationen mit rememberInfiniteTransition Für Animationen in der Endlosschleife (Ladeindikatoren, Pulsiereffekte) bietet `rememberInfiniteTransition` eine eigene API, die keine manuelle Zyklusverwaltung erfordert. ```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) ) } ``` ## Listenanimationen mit LazyColumn Animationen für Listenelemente erfordern besondere Aufmerksamkeit. Der Modifier `animateItem()` (früher `animateItemPlacement`) animiert Umsortierungen automatisch. ```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) } } } } } ``` > **Stabile Keys für Listenanimationen** > > Ohne stabilen `key`-Parameter kann `animateItem` Elemente zwischen Recompositions nicht verfolgen. Eine eindeutige ID statt des Listenindex ist die richtige Wahl. ## Canvas-Animationen Für eigene visuelle Effekte bietet die Kombination aus `Canvas` und animierten Werten maximale Flexibilität. ```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) ) } } ``` ## Performance-Optimierung von Animationen Schlecht optimierte Animationen können zu Jank führen und die Akkulaufzeit verkürzen. Hier sind die bewährten Praktiken, um 60 FPS zu halten. Die erste Regel lautet, Allokationen während der Animation zu vermeiden. Statt Modifiers, die Recompositions auslösen, sollte `graphicsLayer` verwendet werden. ```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() } } ``` Der zweite kritische Punkt betrifft Animationen in Listen. Die Anzahl gleichzeitiger Animationen sollte begrenzt und `derivedStateOf` für abgeleitete Berechnungen genutzt werden. ```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 } ) } } } ``` ## Fazit Animationen in Jetpack Compose bieten ein Gleichgewicht zwischen Bedienkomfort und fortgeschrittener Kontrolle. Hier die wichtigsten Erkenntnisse: - ✅ Die richtige API-Ebene je nach Komplexität wählen (AnimatedVisibility → animate*AsState → Animatable) - ✅ `updateTransition` verwenden, um mehrere zusammenhängende Animationen zu koordinieren - ✅ `spring` für natürliche Animationen und `tween` für präzise Dauer bevorzugen - ✅ Bei Listenanimationen stets einen stabilen `key`-Parameter angeben - ✅ Mit `graphicsLayer` optimieren, um unnötige Recompositions zu vermeiden - ✅ Animationen auf realen Geräten testen, um die Performance zu überprüfen Die Beherrschung von Compose-Animationen unterscheidet professionelle Android-Apps. Diese Techniken, kombiniert mit Aufmerksamkeit für Performance, ermöglichen flüssige und fesselnde Nutzererlebnisse. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/android/jetpack-compose-advanced-animations