אפשר להרחיב את המחלקה המופשטת 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 מופעלות לפני הפונקציות לטיפול ב-PRIORITY_DEFAULT. בתוך כל קבוצת עדיפות, הפונקציות לטיפול בבקשות מופעלות בסדר Last-In, First-Out (LIFO) – הפונקציה לטיפול בבקשות שהוספה לאחרונה מופעלת ראשונה.
יירו של לחצן החזרה באמצעות Jetpack פיתוח נייטיב
ב-Jetpack פיתוח נייטיב, הספרייה מספקת קומפוזבילי לשימוש כדי לנהל את היררכיית ה-dispatcher.
רכיב ה-Composable NavigationBackHandler יוצר NavigationEventHandler לתוכן שלו ומקשר אותו ל-LocalNavigationEventDispatcherOwner. הוא משתמש ב-DisposableEffect של Compose כדי לקרוא אוטומטית לשיטת DisposableEffect של ה-dispatcher כשהקומפוננטה הניתנת להרכבה יוצאת מהמסך, וכך מנהל את המשאבים בצורה בטוחה.dispose()
@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.
גישה לתנועת החלקה אחורה או להחלקה מהקצה במצב כתיבה
איור 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) { 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 } }