# Jetpack Compose: Animazioni Avanzate Passo dopo Passo > Guida completa alle animazioni avanzate in Compose: transizioni, AnimatedVisibility, Animatable, gesture e prestazioni per interfacce Android fluide. - Published: 2026-02-08 - Updated: 2026-04-28 - Author: SharpSkill - Tags: jetpack compose, android, animations, kotlin, ui - Reading time: 12 min --- Le animazioni trasformano un'applicazione funzionale in un'esperienza memorabile. Jetpack Compose offre una potente API dichiarativa che semplifica notevolmente la creazione di interfacce fluide. Questa guida esplora le tecniche avanzate per costruire animazioni performanti e manutenibili. > **Prerequisiti** > > Questo tutorial presuppone familiarità con le basi di Compose (recomposition, stato, modifier). Per le fondamenta conviene consultare prima la guida alle domande di colloquio su Jetpack Compose. ## Fondamenti dell'API di Animazione in Compose Compose mette a disposizione vari livelli di API per le animazioni. La scelta dipende dal livello di controllo desiderato e dalla complessità dell'animazione. L'API si articola in tre categorie principali: animazioni di alto livello (`AnimatedVisibility`, `AnimatedContent`), animazioni basate sullo stato (`animate*AsState`) e animazioni di basso livello (`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) } } ``` Scegliere il livello di API adatto è cruciale per mantenere un codice leggibile pur conservando la flessibilità necessaria. ## AnimatedVisibility: Animazioni Eleganti di Entrata e Uscita `AnimatedVisibility` è il punto di ingresso ideale per animare la comparsa e la scomparsa degli elementi. Questa API gestisce automaticamente la composizione e la decomposizione del contenuto. I parametri `enter` ed `exit` accettano combinazioni di transizioni che definiscono il comportamento dell'animazione. Le transizioni si possono combinare con l'operatore `+`. ```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 ) } } } } ``` Il contenuto all'interno di `AnimatedVisibility` viene composto solo quando `visible = true`, ottimizzando le prestazioni nelle liste con molti elementi espandibili. > **Combinare le transizioni** > > Le transizioni disponibili includono `fadeIn/fadeOut`, `slideIn/slideOut`, `expandIn/shrinkOut`, `scaleIn/scaleOut`. Si possono combinare liberamente per creare effetti personalizzati. ## animate*AsState: Animazioni Guidate dallo Stato La famiglia di funzioni `animate*AsState` anima automaticamente i cambiamenti dei valori primitivi. È l'approccio più idiomatico per le animazioni semplici in Compose. Ogni tipo di dato ha la propria funzione dedicata: `animateColorAsState`, `animateFloatAsState`, `animateDpAsState`, `animateIntAsState` ecc. ```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 ) } } ``` Il parametro `animationSpec` controlla il comportamento temporale dell'animazione. I due spec più usati sono `tween` (durata fissa con easing) e `spring` (fisica realistica con rimbalzo). ## Transition: Orchestrare Più Animazioni Quando più proprietà devono essere animate in modo coordinato, `updateTransition` fornisce un controllo centralizzato. Questa API garantisce che tutte le animazioni restino sincronizzate. Il pattern consiste nel definire uno stato enum e poi creare animazioni per ogni proprietà che dipende da quello stato. ```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: Controllo Totale dell'Animazione `Animatable` è l'API di basso livello che offre un controllo programmatico completo. Questo approccio è necessario per animazioni interrompibili, gesture o scenari complessi. A differenza di `animate*AsState`, `Animatable` permette di fermare, invertire o modificare un'animazione in corso senza attenderne il termine. ```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() } } ``` I metodi chiave di `Animatable` sono `animateTo()` (animare verso un obiettivo), `snapTo()` (cambiamento istantaneo) e `stop()` (interruzione). ## AnimatedContent: Transizioni di Contenuto `AnimatedContent` anima le transizioni tra contenuti differenti. Questa API è perfetta per i cambi di stato che modificano completamente l'interfaccia mostrata. La chiave `targetState` determina quando deve avvenire una transizione. Il `transitionSpec` definisce come interagiscono il contenuto in uscita e quello in entrata. ```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 ) } } ``` > **Prestazioni con AnimatedContent** > > Il contenuto all'interno di `AnimatedContent` viene ricomposto a ogni cambio di `targetState`. Per contenuti complessi, conviene memoizzare gli elementi costosi o usare una strategia di chiavi adeguata. ## Animazioni Infinite con rememberInfiniteTransition Per animazioni in loop (indicatori di caricamento, effetti pulsanti), `rememberInfiniteTransition` fornisce un'API dedicata che non richiede gestione manuale del ciclo. ```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) ) } ``` ## Animazioni di Liste con LazyColumn Le animazioni degli elementi di una lista richiedono attenzione particolare. Il modifier `animateItem()` (in passato `animateItemPlacement`) anima automaticamente i riordinamenti. ```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) } } } } } ``` > **Chiavi stabili per le animazioni di lista** > > Senza un parametro `key` stabile, `animateItem` non riesce a tracciare gli elementi tra le ricomposizioni. Conviene usare un ID univoco invece dell'indice della lista. ## Animazioni con Canvas Per effetti visivi personalizzati, combinare `Canvas` con valori animati offre la massima flessibilità. ```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) ) } } ``` ## Ottimizzazione delle Prestazioni delle Animazioni Le animazioni mal ottimizzate possono provocare jank e scaricare la batteria. Ecco le buone pratiche per mantenere i 60 FPS. La prima regola consiste nell'evitare le allocazioni durante l'animazione. Conviene usare `graphicsLayer` invece di modifier che innescano ricomposizioni. ```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() } } ``` Il secondo punto critico riguarda le animazioni nelle liste. Conviene limitare il numero di animazioni simultanee e usare `derivedStateOf` per i calcoli derivati. ```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 } ) } } } ``` ## Conclusione Le animazioni in Jetpack Compose offrono un equilibrio tra facilità d'uso e controllo avanzato. Ecco i punti chiave da ricordare: - ✅ Scegliere il livello di API adeguato in base alla complessità (AnimatedVisibility → animate*AsState → Animatable) - ✅ Usare `updateTransition` per coordinare più animazioni correlate - ✅ Preferire `spring` per animazioni naturali e `tween` per durate precise - ✅ Fornire sempre un parametro `key` stabile per le animazioni di lista - ✅ Ottimizzare con `graphicsLayer` per evitare ricomposizioni inutili - ✅ Testare le animazioni su dispositivi reali per validarne le prestazioni Padroneggiare le animazioni di Compose distingue le applicazioni Android professionali. Queste tecniche, unite all'attenzione per le prestazioni, permettono di creare esperienze utente fluide e coinvolgenti. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/it/blog/android/jetpack-compose-advanced-animations