Puedes extender la clase abstracta NavigationEventHandler para controlar los eventos de navegación en todas las plataformas. Esta clase proporciona métodos correspondientes al ciclo de vida de un gesto de navegación.
val myHandler = object: NavigationEventHandler<NavigationEventInfo>( initialInfo = NavigationEventInfo.None, isBackEnabled = true ) { override fun onBackStarted(event: NavigationEvent) { // Prepare for the back event } override fun onBackProgressed(event: NavigationEvent) { // Use event.progress for predictive animations } // This is the required method for final event handling override fun onBackCompleted() { // Complete the back event } override fun onBackCancelled() { // Cancel the back event } }
La función addHandler conecta el controlador al distribuidor:
navigationEventDispatcher.addHandler(myHandler)
Llama a myHandler.remove() para quitar el controlador del dispatcher:
myHandler.remove()
Los controladores se invocan según la prioridad y, luego, según la antigüedad. Se llama a todos los controladores de PRIORITY_OVERLAY antes que a los controladores de PRIORITY_DEFAULT. Dentro de cada grupo de prioridad, los controladores se invocan en orden LIFO (último en entrar, primero en salir): el controlador agregado más recientemente se llama primero.
Cómo interceptar el botón Atrás con Jetpack Compose
En el caso de Jetpack Compose, la biblioteca proporciona un elemento componible de utilidad para administrar la jerarquía del dispatcher.
El elemento NavigationBackHandler componible crea un NavigationEventHandler para su contenido y lo vincula al LocalNavigationEventDispatcherOwner. Usa DisposableEffect de Compose para llamar automáticamente al método dispose() del dispatcher cuando el elemento componible abandona la pantalla, lo que permite administrar los recursos de forma segura.
@Composable public fun NavigationBackHandler( state: NavigationEventState<out NavigationEventInfo>, isBackEnabled: Boolean = true, onBackCancelled: () -> Unit = {}, onBackCompleted: () -> Unit, ){ }
Esta función te permite controlar el procesamiento de eventos con precisión dentro de subárboles de IU localizados.
@Composable fun HandlingBackWithTransitionState( onNavigateUp: () -> Unit ) { val navigationState = rememberNavigationEventState( currentInfo = NavigationEventInfo.None ) val transitionState = navigationState.transitionState // React to predictive back transition updates when (transitionState) { is NavigationEventTransitionState.InProgress -> { val progress = transitionState.latestEvent.progress // Use progress (0f..1f) to update UI during the gesture } is NavigationEventTransitionState.Idle -> { // Reset any temporary UI state if the gesture is cancelled } } NavigationBackHandler( state = navigationState, onBackCancelled = { // Called if the back gesture is cancelled }, onBackCompleted = { // Called when the back gesture fully completes onNavigateUp() } ) }
En este ejemplo, se muestra cómo observar las actualizaciones del gesto de atrás predictivo con NavigationEventTransitionState. El valor de progress se puede usar para actualizar los elementos de la IU en respuesta al gesto de atrás, mientras se controla la finalización y la cancelación a través de NavigationBackHandler.
Cómo acceder al gesto de atrás o al borde de deslizamiento en Compose
Figura 1. Animación del gesto atrás predictivo creada con NavigationEvent y Compose.
Para animar la pantalla mientras el usuario desliza el dedo hacia atrás, deberás (a) verificar si NavigationEventTransitionState es InProgress y (b) observar el progreso y el estado del borde de deslizamiento con rememberNavigationEventState:
progress: Es un número de punto flotante de0.0a1.0que indica qué tanto deslizó el usuario.swipeEdge: Es una constante entera (EDGE_LEFToEDGE_RIGHT) que indica dónde comenzó el gesto.
El siguiente fragmento es un ejemplo simplificado de cómo implementar una animación de escala y desplazamiento:
object Routes { const val SCREEN_A = "Screen A" const val SCREEN_B = "Screen B" } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { var state by remember { mutableStateOf(Routes.SCREEN_A) } val backEventState = rememberNavigationEventState<NavigationEventInfo>(currentInfo = NavigationEventInfo.None) when (state) { Routes.SCREEN_A -> { ScreenA(onNavigate = { state = Routes.SCREEN_B }) } else -> { if (backEventState.transitionState is NavigationEventTransitionState.InProgress) { ScreenA(onNavigate = { }) } ScreenB( backEventState = backEventState, onBackCompleted = { state = Routes.SCREEN_A } ) } } } } } @Composable fun ScreenB( backEventState: NavigationEventState<NavigationEventInfo>, onBackCompleted: () -> Unit = {}, ) { val transitionState = backEventState.transitionState val latestEvent = (transitionState as? NavigationEventTransitionState.InProgress) ?.latestEvent val backProgress = latestEvent?.progress ?: 0f val swipeEdge = latestEvent?.swipeEdge ?: NavigationEvent.EDGE_LEFT if (transitionState is NavigationEventTransitionState.InProgress) { Log.d("BackGesture", "Progress: ${transitionState.latestEvent.progress}") } else if (transitionState is NavigationEventTransitionState.Idle) { Log.d("BackGesture", "Idle") } val animatedScale by animateFloatAsState( targetValue = 1f - (backProgress * 0.1f), label = "ScaleAnimation" ) val windowInfo = LocalWindowInfo.current val density = LocalDensity.current val maxShift = remember(windowInfo, density) { val widthDp = with(density) { windowInfo.containerSize.width.toDp() } (widthDp.value / 20f) - 8 } val offsetX = when (swipeEdge) { EDGE_LEFT -> (backProgress * maxShift).dp EDGE_RIGHT -> (-backProgress * maxShift).dp else -> 0.dp } NavigationBackHandler( state = backEventState, onBackCompleted = onBackCompleted, isBackEnabled = true ) Box( modifier = Modifier .offset(x = offsetX) .scale(animatedScale) ){ // Rest of UI } }