處理返回手勢和預測返回動畫

您可以擴充抽象類別 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 函式會將處理常式連結至調度器:

navigationEventDispatcher.addHandler(myHandler)

呼叫 myHandler.remove(),從調度器中移除處理常式:

myHandler.remove()

系統會依優先順序和最近使用時間叫用處理常式。所有 PRIORITY_OVERLAY 處理常式都會在任何 PRIORITY_DEFAULT 處理常式之前呼叫。在每個優先順序群組中,處理常式會依後進先出 (LIFO) 順序叫用,也就是最晚加入的處理常式會先呼叫。

使用 Jetpack Compose 攔截返回操作

如果是 Jetpack Compose,程式庫會提供公用程式可組合函式,用於管理調度器階層。

NavigationBackHandler 可組合函式會為內容建立 NavigationEventHandler,並連結至 LocalNavigationEventDispatcherOwner。當可組合函式離開畫面時,系統會使用 Compose 的 DisposableEffect 自動呼叫調度器的 dispose() 方法,安全地管理資源。

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

}

這項函式可讓您精確控管本地化 UI 子樹狀結構中的事件處理作業。

@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 值可用於更新 UI 元素,以回應返回手勢,同時透過 NavigationBackHandler 處理完成和取消作業。

在 Compose 中存取返回手勢或滑動邊緣

圖 1. 使用 NavigationEvent 和 Compose 建立的預測返回動畫。

如要在使用者滑動返回時為畫面建立動畫,您需要 (a) 檢查 NavigationEventTransitionState 是否為 InProgress,以及 (b) 使用 rememberNavigationEventState 觀察進度和滑動邊緣狀態:

  • progress:從 0.01.0 的浮點值,表示使用者滑動的距離。
  • swipeEdge:整數常數 (EDGE_LEFTEDGE_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
    }
}