אפשר להרחיב את המחלקה המופשטת NavigationEventHandler כדי לטפל באירועי ניווט בפלטפורמות שונות. הכיתה הזו מספקת שיטות שתואמות למחזור החיים של תנועת ניווט.
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 } }
הפונקציה addHandler מחברת את ה-handler ל-dispatcher:
navigationEventDispatcher.addHandler(myHandler)
קוראים ל-myHandler.remove() כדי להסיר את ה-handler מה-dispatcher:
myHandler.remove()
הפונקציות לטיפול באירועים מופעלות לפי עדיפות, ואז לפי עדכניות. כל ה-handlers של PRIORITY_OVERLAY נקראים לפני ה-handlers של PRIORITY_DEFAULT. בתוך כל קבוצת עדיפות, ה-handlers מופעלים בסדר "נכנס אחרון, יוצא ראשון" (LIFO) – ה-handler שנוסף אחרון מופעל ראשון.
יירוט של חזרה אחורה באמצעות Jetpack Compose
ב-Jetpack Compose, הספרייה מספקת פונקציה קומפוזבילית לניהול ההיררכיה של ה-dispatcher.
הפונקציה הקומפוזבילית NavigationBackHandler יוצרת NavigationEventHandler עבור התוכן שלה ומקשרת אותו אל LocalNavigationEventDispatcherOwner. היא משתמשת ב-DisposableEffect של Compose כדי להפעיל באופן אוטומטי את השיטה dispose() של הדיספצ'ר כשהרכיב ה-composable יוצא מהמסך, וכך לנהל את המשאבים בצורה בטוחה.
@Composable public fun NavigationBackHandler( state: NavigationEventState<out NavigationEventInfo>, isBackEnabled: Boolean = true, onBackCancelled: () -> Unit = {}, onBackCompleted: () -> Unit, ){ }
הפונקציה הזו מאפשרת לכם לשלוט בטיפול באירועים באופן מדויק בתוך עצי משנה של ממשק משתמש מקומי.
@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() } ) }
בדוגמה הזו אפשר לראות איך צופים בעדכונים של חיזוי תנועת החזרה באמצעות NavigationEventTransitionState. אפשר להשתמש בערך progress כדי לעדכן רכיבים בממשק המשתמש בתגובה לתנועת החזרה, ולטפל בהשלמה ובביטול באמצעות NavigationBackHandler.
איך לגשת לתנועת החזרה או להחלקה מהקצה ב-Compose
איור 1. אנימציה של חיזוי החזרה שנוצרה באמצעות NavigationEvent ו-Compose.
כדי להוסיף אנימציה למסך בזמן שהמשתמש מחליק אחורה, צריך (א) לבדוק אם
NavigationEventTransitionState הוא InProgress, ו-(ב) לצפות
בהתקדמות ובמצב של קצה ההחלקה באמצעות rememberNavigationEventState:
-
progress: מספר ממשי (float) מ-0.0עד1.0שמציין את המרחק שהמשתמש החליק. -
swipeEdge: קבוע של מספר שלם (EDGE_LEFTאוEDGE_RIGHT) שמציין איפה התנועה התחילה.
בקטע הקוד הבא אפשר לראות דוגמה פשוטה לאופן שבו מטמיעים אנימציה של שינוי קנה מידה והזזה:
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 } }