จัดการท่าทางสัมผัสย้อนกลับและภาพเคลื่อนไหวของการย้อนกลับที่คาดการณ์ได้

คุณขยายคลาส Abstract 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 ไลบรารีมี Composable ยูทิลิตีเพื่อจัดการ ลำดับชั้นของ Dispatcher

Composable NavigationBackHandler จะสร้าง NavigationEventHandler สำหรับ เนื้อหาของตัวเองและลิงก์กับ LocalNavigationEventDispatcherOwner โดยจะใช้ DisposableEffect ของ Compose เพื่อเรียกใช้เมธอด dispose() ของ Dispatcher โดยอัตโนมัติ เมื่อ Composable ออกจากหน้าจอ ซึ่งจะจัดการทรัพยากรอย่างปลอดภัย

@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

เข้าถึงท่าทางสัมผัสย้อนกลับหรือปัดขอบในฟีเจอร์ช่วยเขียน

รูปที่ 1 ภาพเคลื่อนไหวของการย้อนกลับที่คาดการณ์ได้ซึ่งสร้างด้วย NavigationEvent และ Compose

หากต้องการเคลื่อนไหวหน้าจอขณะที่ผู้ใช้ปัดกลับ คุณจะต้อง (ก) ตรวจสอบว่า NavigationEventTransitionState เป็น InProgress หรือไม่ และ (ข) สังเกตความคืบหน้าและสถานะขอบการปัดด้วย rememberNavigationEventState

  • progress: ค่าทศนิยมจาก 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
    }
}