„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 nach 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 der Reihenfolge „Last-In, First-Out“ (LIFO) aufgerufen. Der zuletzt hinzugefügte Handler wird zuerst aufgerufen.

Zurück-Geste mit Jetpack Compose abfangen

Für Jetpack Compose bietet die Bibliothek ein zusammensetzbares Dienstprogramm zum Verwalten der Dispatcher-Hierarchie.

Das Compose-Element NavigationBackHandler erstellt ein NavigationEventHandler für seinen Inhalt und verknüpft es mit dem LocalNavigationEventDispatcherOwner. Dabei wird DisposableEffect von Compose verwendet, um automatisch die Methode dispose() 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 Ereignisbehandlung 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 Aktualisierungen der intelligenten „Zurück“-Touchgeste mit NavigationEventTransitionState beobachten können. Der Wert progress kann verwendet werden, um UI-Elemente als Reaktion auf die „Zurück“-Geste zu aktualisieren, während der Abschluss und der Abbruch über NavigationBackHandler verarbeitet werden.

In Compose auf die Geste „Zurück“ oder den Wischrand zugreifen

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

Wenn du den Bildschirm animieren möchtest, während der Nutzer zurückwischt, musst du (a) prüfen, ob NavigationEventTransitionState den Wert InProgress hat, und (b) den Fortschritt und den Status des Wischrands mit rememberNavigationEventState beobachten:

  • progress: Ein Gleitkommawert von 0.0 bis 1.0, der angibt, wie weit der Nutzer gewischt hat.
  • swipeEdge: Eine Ganzzahlkonstante (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) {
        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
    }
}