Xử lý cử chỉ quay lại và ảnh động xem trước thao tác quay lại

Bạn có thể mở rộng lớp trừu tượng NavigationEventHandler để xử lý các sự kiện điều hướng trên nhiều nền tảng. Lớp này cung cấp các phương thức tương ứng với vòng đời của một cử chỉ điều hướng.

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

Hàm addHandler kết nối trình xử lý với trình điều phối:

navigationEventDispatcher.addHandler(myHandler)

Gọi myHandler.remove() để xoá trình xử lý khỏi trình điều phối:

myHandler.remove()

Các trình xử lý được gọi dựa trên mức độ ưu tiên, sau đó là thời gian truy cập gần đây nhất. Tất cả các trình xử lý PRIORITY_OVERLAY đều được gọi trước mọi trình xử lý PRIORITY_DEFAULT. Trong mỗi nhóm ưu tiên, các trình xử lý được gọi theo thứ tự Vào sau ra trước (LIFO) – trình xử lý được thêm gần đây nhất sẽ được gọi trước.

Chặn thao tác quay lại bằng Jetpack Compose

Đối với Jetpack Compose, thư viện này cung cấp một thành phần kết hợp tiện ích để quản lý hệ phân cấp điều phối.

Thành phần kết hợp NavigationBackHandler tạo một NavigationEventHandler cho nội dung của thành phần đó và liên kết nội dung đó với LocalNavigationEventDispatcherOwner. Thao tác này sử dụng DisposableEffect của Compose để tự động gọi phương thức dispose() của trình điều phối khi thành phần kết hợp rời khỏi màn hình, giúp quản lý tài nguyên một cách an toàn.

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

}

Hàm này cho phép bạn kiểm soát chính xác việc xử lý sự kiện trong các cây con của giao diện người dùng được bản địa hoá.

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

Ví dụ này cho thấy cách theo dõi thông tin cập nhật về xem trước thao tác quay lại bằng NavigationEventTransitionState. Bạn có thể dùng giá trị progress để cập nhật các phần tử trên giao diện người dùng nhằm phản hồi cử chỉ quay lại, đồng thời xử lý việc hoàn tất và huỷ thông qua NavigationBackHandler.

Truy cập vào cử chỉ quay lại hoặc vuốt cạnh trong Compose

Hình 1 Ảnh động xem trước thao tác quay lại được tạo bằng NavigationEvent và Compose.

Để tạo ảnh động cho màn hình trong khi người dùng vuốt ngược lại, bạn cần (a) kiểm tra xem NavigationEventTransitionState có phải là InProgress hay không và (b) theo dõi tiến trình và trạng thái cạnh vuốt bằng rememberNavigationEventState:

  • progress: Một số thực từ 0.0 đến 1.0 cho biết khoảng cách mà người dùng đã vuốt.
  • swipeEdge: Hằng số nguyên (EDGE_LEFT hoặc EDGE_RIGHT) cho biết vị trí bắt đầu của cử chỉ.

Đoạn mã sau đây là một ví dụ đơn giản về cách triển khai ảnh động thu phóng và dịch chuyển:

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