Cómo animar entre destinos

NavDisplay proporciona funciones de animación integradas para crear transiciones visuales fluidas a medida que los usuarios navegan por tu app. Puedes personalizar estas animaciones de forma global para NavDisplay o a nivel de Scene con metadatos.

Comprender las capacidades de animación integradas

NavDisplay usa la API de ContentTransform para definir cómo se anima el contenido durante la navegación. NavDisplay anima automáticamente las transiciones entre escenas cuando cambia una clave derivada de la clase de la escena actual y su propiedad key. Cuando cambia esta clave, NavDisplay usa el ContentTransform para el tipo de transición (hacia adelante, hacia atrás o atrás predictivo) de la escena adecuada en la transición. Si no se define ese ContentTransform, NavDisplay recurre a su transición predeterminada correspondiente.

Anula las transiciones predeterminadas

Puedes anular los comportamientos de animación predeterminados proporcionando parámetros de transición a NavDisplay.

  • transitionSpec: Este parámetro define la ContentTransform que se aplicará cuando se agregue contenido a la pila de actividades (es decir, cuando se navegue hacia adelante).
  • popTransitionSpec: Este parámetro define la ContentTransform que se aplicará cuando se quite contenido de la pila de actividades (es decir, cuando se navegue hacia atrás).
  • predictivePopTransitionSpec: Este parámetro define la ContentTransform que se aplicará cuando se quite el contenido con un gesto atrás predictivo.

Anula las transiciones en el nivel Scene

Puedes usar metadatos para definir animaciones personalizadas para escenas individuales con las siguientes claves de metadatos que define NavDisplay:

Cuando se proporcionan, estas transiciones a nivel de la escena se usan en lugar de los valores predeterminados correspondientes establecidos en NavDisplay.

En el siguiente fragmento, se muestran las transiciones globales de NavDisplay y una anulación en el nivel de NavEntry individual:

@Serializable
data object ScreenA : NavKey

@Serializable
data object ScreenB : NavKey

@Serializable
data object ScreenC : NavKey

class AnimatedNavDisplayActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {

            Scaffold { paddingValues ->

                val backStack = rememberNavBackStack(ScreenA)

                NavDisplay(
                    backStack = backStack,
                    onBack = { backStack.removeLastOrNull() },
                    entryProvider = entryProvider {
                        entry<ScreenA> {
                            ContentOrange("This is Screen A") {
                                Button(onClick = { backStack.add(ScreenB) }) {
                                    Text("Go to Screen B")
                                }
                            }
                        }
                        entry<ScreenB> {
                            ContentMauve("This is Screen B") {
                                Button(onClick = { backStack.add(ScreenC) }) {
                                    Text("Go to Screen C")
                                }
                            }
                        }
                        entry<ScreenC>(
                            metadata = metadata {
                                put(NavDisplay.TransitionKey) {
                                    // Slide new content up, keeping the old content in place underneath
                                    slideInVertically(
                                        initialOffsetY = { it },
                                        animationSpec = tween(1000)
                                    ) togetherWith ExitTransition.KeepUntilTransitionsFinished
                                }
                                put(NavDisplay.PopTransitionKey) {
                                    // Slide old content down, revealing the new content in place underneath
                                    EnterTransition.None togetherWith
                                            slideOutVertically(
                                                targetOffsetY = { it },
                                                animationSpec = tween(1000)
                                            )
                                }
                                put(NavDisplay.PredictivePopTransitionKey) {
                                    // Slide old content down, revealing the new content in place underneath
                                    EnterTransition.None togetherWith
                                            slideOutVertically(
                                                targetOffsetY = { it },
                                                animationSpec = tween(1000)
                                            )
                                }
                            }
                        ) {
                            ContentGreen("This is Screen C")
                        }
                    },
                    transitionSpec = {
                        // Slide in from right when navigating forward
                        slideInHorizontally(initialOffsetX = { it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { -it })
                    },
                    popTransitionSpec = {
                        // Slide in from left when navigating back
                        slideInHorizontally(initialOffsetX = { -it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { it })
                    },
                    predictivePopTransitionSpec = {
                        // Slide in from left when navigating back
                        slideInHorizontally(initialOffsetX = { -it }) togetherWith
                            slideOutHorizontally(targetOffsetX = { it })
                    },
                    modifier = Modifier.padding(paddingValues)
                )
            }
        }
    }
}

Figura 1: App con animaciones personalizadas

Transición de entradas de navegación entre escenas

En las apps que crean diseños personalizados con escenas, es posible que se incluya un NavEntry en la propiedad entries de ambas escenas durante una transición. De forma interna, NavDisplay verifica que cada entrada se muestre en, como máximo, una escena en cualquier momento, lo que puede generar transiciones inestables cuando cambia la escena que renderiza un NavEntry. Para animar de forma fluida las entradas entre escenas, puedes unir tu NavDisplay en un SharedTransitionLayout y proporcionar el SharedTransitionScope al NavDisplay, como se muestra en el siguiente ejemplo:

SharedTransitionLayout {
    NavDisplay(
        // ...
        sharedTransitionScope = this
    )
}

Figura 2: Transición inestable cuando no se pasa el SharedTransitionScope al NavDisplay.
Figura 3. Transición fluida cuando se pasa de SharedTransitionScope a NavDisplay