Puedes extender la clase abstracta NavigationEventHandler para controlar los eventos de navegación en todas las plataformas. Esta clase proporciona métodos que corresponden 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 despachador:
navigationEventDispatcher.addHandler(myHandler)
Llama a myHandler.remove() para quitar el controlador del despachador:
myHandler.remove()
Los controladores se invocan según la prioridad y, luego, según la actualidad. Se llama a todos los controladores PRIORITY_OVERLAY antes que a los controladores PRIORITY_DEFAULT. Dentro de cada grupo de prioridad, los controladores se invocan en un orden de último en entrar,
primero en salir (LIFO): el controlador agregado más recientemente se llama primero.
Cómo interceptar el botón Atrás con Jetpack Compose
Para Jetpack Compose, la biblioteca proporciona un elemento componible de utilidad para administrar la jerarquía de despachadores.
El elemento componible NavigationBackHandler crea un NavigationEventHandler para su contenido y lo vincula al LocalNavigationEventDispatcherOwner. Usa DisposableEffect de Compose para llamar automáticamente al método dispose() del despachador cuando la función de componibilidad sale de la pantalla, lo que administra 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 manejo 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 atrás predictivo con NavigationEventTransitionState. El valor progress se puede usar para actualizar los elementos de la IU en respuesta al gesto de retroceso, 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. Una animación del gesto atrás predictivo compilada 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 valor de punto flotante de0.0a1.0que indica qué tan lejos deslizó el usuario.swipeEdge: Es una constante de número entero (EDGE_LEFToEDGE_RIGHT) que indica dónde comenzó el gesto.
En el siguiente fragmento, se muestra 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) { NavigationEvent.EDGE_LEFT -> (backProgress * maxShift).dp NavigationEvent.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 } }