Vous pouvez étendre la classe abstraite NavigationEventHandler pour gérer les événements de navigation sur les plates-formes. Cette classe fournit des méthodes correspondant au cycle de vie d'un geste de navigation.
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 fonction addHandler connecte le gestionnaire au répartiteur :
navigationEventDispatcher.addHandler(myHandler)
Appelez myHandler.remove() pour supprimer le gestionnaire du répartiteur :
myHandler.remove()
Les gestionnaires sont appelés en fonction de leur priorité, puis de leur ancienneté. Tous les gestionnaires PRIORITY_OVERLAY sont appelés avant les gestionnaires PRIORITY_DEFAULT. Dans chaque groupe de priorité, les gestionnaires sont appelés dans l'ordre LIFO (dernier entré, premier sorti) : le gestionnaire ajouté le plus récemment est appelé en premier.
Intercepter le retour avec Jetpack Compose
Pour Jetpack Compose, la bibliothèque fournit un composable utilitaire permettant de gérer la hiérarchie des répartiteurs.
Le composable NavigationBackHandler crée un NavigationEventHandler pour son contenu et l'associe au LocalNavigationEventDispatcherOwner. Il utilise DisposableEffect de Compose pour appeler automatiquement la méthode dispose() du répartiteur lorsque le composable quitte l'écran, ce qui permet de gérer les ressources de manière sécurisée.
@Composable public fun NavigationBackHandler( state: NavigationEventState<out NavigationEventInfo>, isBackEnabled: Boolean = true, onBackCancelled: () -> Unit = {}, onBackCompleted: () -> Unit, ){ }
Cette fonction vous permet de contrôler précisément la gestion des événements dans les sous-arborescences d'UI localisées.
@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() } ) }
Cet exemple montre comment observer les mises à jour des gestes de retour prédictifs à l'aide de NavigationEventTransitionState. La valeur progress peut être utilisée pour mettre à jour les éléments d'interface utilisateur en réponse au geste Retour, tout en gérant l'achèvement et l'annulation via NavigationBackHandler.
Accéder au geste de retour ou au balayage sur la bordure dans Compose
Figure 1 : Animation de la prévisualisation du Retour créée avec NavigationEvent et Compose.
Pour animer l'écran pendant que l'utilisateur effectue un balayage vers l'arrière, vous devez (a) vérifier si NavigationEventTransitionState est InProgress et (b) observer l'état de progression et de bordure de balayage avec rememberNavigationEventState :
progress: valeur float comprise entre0.0et1.0indiquant la distance parcourue par l'utilisateur lors du balayage.swipeEdge: constante entière (EDGE_LEFTouEDGE_RIGHT) indiquant où le geste a commencé.
L'extrait suivant est un exemple simplifié d'implémentation d'une animation de mise à l'échelle et de décalage :
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 } }