„Zurück“-Touchgesten und Animationen für intelligente „Zurück“-Touchgeste verarbeiten

Sie können die abstrakte Klasse NavigationEventHandler erweitern, um Navigationsereignisse plattformübergreifend zu verarbeiten. Diese Klasse bietet Methoden, die dem Lebenszyklus einer Navigationsgeste entsprechen.

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
    }
}

Die Funktion addHandler verbindet den Handler mit dem Dispatcher:

navigationEventDispatcher.addHandler(myHandler)

Rufen Sie myHandler.remove() auf, um den Handler aus dem Dispatcher zu entfernen:

myHandler.remove()

Handler werden basierend auf der Priorität und dann nach Aktualität aufgerufen. Alle PRIORITY_OVERLAY-Handler werden vor allen PRIORITY_DEFAULT-Handlern aufgerufen. Innerhalb jeder Prioritätsgruppe werden Handler in LIFO-Reihenfolge (Last-In, First-Out) aufgerufen. Der zuletzt hinzugefügte Handler wird zuerst aufgerufen.

„Zurück“-Vorgang mit Jetpack Compose abfangen

Für Jetpack Compose bietet die Bibliothek eine zusammensetzbare Dienstprogrammfunktion zum Verwalten der Dispatcher-Hierarchie.

Die zusammensetzbare Funktion NavigationBackHandler erstellt ein NavigationEventHandler für ihren Inhalt und verknüpft es mit dem LocalNavigationEventDispatcherOwner. Dabei wird die DisposableEffect-Funktion von Compose verwendet, um automatisch die dispose()-Methode des Dispatchers aufzurufen, wenn das Composable den Bildschirm verlässt. So werden Ressourcen sicher verwaltet.

@Composable
public fun NavigationBackHandler(
    state: NavigationEventState<out NavigationEventInfo>,
    isBackEnabled: Boolean = true,
    onBackCancelled: () -> Unit = {},
    onBackCompleted: () -> Unit,
){

}

Mit dieser Funktion können Sie die Ereignisverarbeitung in lokalisierten UI-Unterbäumen genau steuern.

@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()
        }
    )
}

In diesem Beispiel wird gezeigt, wie Sie Vorhersage-Updates für die Zurück-Geste mit NavigationEventTransitionState beobachten. Der progress-Wert kann verwendet werden, um UI-Elemente als Reaktion auf die Zurück-Geste zu aktualisieren, während die Vervollständigung und das Abbrechen über NavigationBackHandler verarbeitet werden.

Auf die Zurück-Geste oder das Wischen am Rand in Compose zugreifen

Abbildung 1. Eine Animation für die intelligente „Zurück“-Touchgeste, die mit NavigationEvent und Compose erstellt wurde.

Wenn Sie den Bildschirm animieren möchten, während der Nutzer zurückwischt, müssen Sie (a) prüfen, ob NavigationEventTransitionState gleich InProgress ist, und (b) den Fortschritt und den Status des Wischrands mit rememberNavigationEventState beobachten:

  • progress: Eine Gleitkommazahl zwischen 0.0 und 1.0, die angibt, wie weit der Nutzer gewischt hat.
  • swipeEdge: Eine ganzzahlige Konstante (EDGE_LEFT oder EDGE_RIGHT), die angibt, wo die Geste begonnen hat.

Das folgende Snippet ist ein vereinfachtes Beispiel für die Implementierung einer Skalierungs- und Verschiebungsanimation:

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
    }
}