เพจจิ้งด้วย Compose สำหรับ Wear OS

การแบ่งหน้าช่วยให้ผู้ใช้ปัดในแนวนอนหรือแนวตั้งระหว่างหน้าจอแบบเต็ม ที่แตกต่างกันบนอุปกรณ์ Wear OS กรณีการใช้งานที่พบบ่อย ได้แก่ การปัดระหว่างเมตริกการออกกำลังกาย และการควบคุมสื่อในแอปออกกำลังกาย หรือการไปยังโฟลว์แบบหลายหน้า

ใน Compose สำหรับ Wear OS Material 3 HorizontalPagerScaffold และ VerticalPagerScaffold จะประสานงานเลย์เอาต์ของ Pager โดยจะจัดตำแหน่งตัวบ่งหน้าและจัดการการเปลี่ยนโดยอัตโนมัติด้วย TimeText แต่ละหน้าจะอยู่ใน Composable AnimatedPage ซึ่งใช้ การปรับขนาด การเปลี่ยนมุมโค้ง และเอฟเฟกต์ขอบมืดเมื่อหน้าเปลี่ยนผ่าน จอแสดงผลทรงกลม

ภาพเคลื่อนไหวต่อไปนี้แสดงวิธีที่ HorizontalPagerScaffold และ AnimatedPage ปรับขนาดและเคลื่อนไหวหน้าเว็บระหว่างการปัดในแนวนอน

โครงสร้างลำดับชั้นสำหรับการแบ่งหน้า

เมื่อออกแบบเลย์เอาต์หน้าจอแบบแบ่งหน้าใน Material 3 ไม่ว่าจะปัดไปทางซ้ายหรือขวา (HorizontalPager) หรือขึ้นหรือลง (VerticalPager) ให้ใช้ลำดับชั้นของคอมโพเนนต์ต่อไปนี้จากคอนเทนเนอร์ด้านนอกไปยังเนื้อหาด้านใน

  1. AppScaffold: คอนเทนเนอร์นอกสุดที่รูทของแอป (ใช้เพียง 1 รายการต่อแอป) โดยจะยึดTimeTextภาพซ้อนทับส่วนกลางเพื่อให้ นาฬิกาคงที่ที่ด้านบนของหน้าจอในระหว่างการเปลี่ยนหน้า และท่าทางสัมผัสปัดเพื่อปิด
  2. HorizontalPagerScaffold หรือ VerticalPagerScaffold: วางไว้ ภายใน AppScaffold ที่ระดับเพจเจอร์ โดยจะประสานงานการเปลี่ยนผ่านระหว่าง TimeText กับ HorizontalPageIndicator หรือ VerticalPageIndicator
  3. HorizontalPager หรือ VerticalPager: คอนเทนเนอร์ Pager พื้นฐาน ที่จัดการท่าทางสัมผัสการปัด ฟิสิกส์การสะบัด และการสแนปการใส่ข้อมูลด้วยปุ่มหมุน โดยใช้ PagerState ที่ใช้ร่วมกัน
  4. AnimatedPage: วางไว้ใน Lambda เนื้อหาหน้าของเครื่องมือเปลี่ยนหน้า โดยจะ ครอบแต่ละหน้าเพื่อใช้ภาพเคลื่อนไหวการเปลี่ยนหน้าของ Material 3 (เอฟเฟกต์การปรับขนาดและเลเยอร์มืด) ตามออฟเซ็ตของหน้าใน PagerState
  5. ScreenScaffold: วางภายใน AnimatedPage สำหรับแต่ละหน้า แต่ละหน้า เนื่องจากแต่ละหน้าอาจมีรายการที่เลื่อนได้ในแนวตั้งของตัวเอง (เช่น TransformingLazyColumn) หรือEdgeButtonของตัวเอง การซ้อน ScreenScaffold ภายในแต่ละหน้าจะช่วยให้มั่นใจได้ว่าตัวบ่งชี้การเลื่อนแนวตั้ง ปุ่มขอบ และ contentPadding จะเป็นของหน้าเว็บนั้นๆ และเคลื่อนไหวไปพร้อมกับหน้าเว็บอย่างราบรื่น
AppScaffold (1 per app: anchors global TimeText)
 └── HorizontalPagerScaffold / VerticalPagerScaffold (manages PageIndicator)
      └── HorizontalPager / VerticalPager (manages PagerState & fling behavior)
           └── AnimatedPage (applies scaling & scrim transitions per page)
                └── ScreenScaffold (1 per page: ScrollIndicator & EdgeButton)
                     └── Page Content (Column or TransformingLazyColumn)

ใช้เพจเจอร์แนวนอน

หากต้องการใช้เพจเจอร์แนวนอนใน Material 3 ให้ซ้อน HorizontalPagerScaffold, HorizontalPager, AnimatedPage และ ScreenScaffold ไว้ใน AppScaffold ของแอป

ใช้ PagerScaffoldDefaults.snapWithSpringFlingBehavior สำหรับ flingBehavior เพื่อใช้ฟิสิกส์การเคลื่อนไหวแบบสปริงของ Material 3 และการสแนปหน้าเว็บที่ตอบสนอง (HighSnapPositionalThreshold) โดยค่าเริ่มต้น HorizontalPager จะปิดใช้ การเลื่อนหน้าแบบหมุน (rotaryScrollableBehavior = null) ซึ่งจะช่วยให้การหมุน เม็ดมะยมหรือการป้อนข้อมูลขอบจอเลื่อนรายการแนวตั้ง (เช่น TransformingLazyColumn) ภายในหน้าเว็บที่ใช้งานอยู่ได้

ตัวอย่างต่อไปนี้แสดงการตั้งค่า HorizontalPagerScaffold ที่สมบูรณ์

@Composable
fun HorizontalPagerScaffoldSample(navigateBack: () -> Unit) {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 10 })

        HorizontalPagerScaffold(pagerState = pagerState) {
            HorizontalPager(
                state = pagerState,
                flingBehavior =
                    PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                        state = pagerState
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center,
                        ) {
                            Text(text = "Page #$page")
                            Spacer(modifier = Modifier.height(8.dp))
                            Text(text = "Swipe left and right")
                            if (page == 0) {
                                Spacer(modifier = Modifier.height(16.dp))
                                Button(onClick = navigateBack) { Text("Exit") }
                            }
                        }
                    }
                }
            }
        }
    }
}

หากหน้าภายในเพจเจอร์แนวนอนมี TransformingLazyColumn ให้สร้าง rememberTransformingLazyColumnState() แยกต่างหากภายในขอบเขตของหน้าเว็บนั้น แล้วส่งไปยัง ScreenScaffold และ TransformingLazyColumn ด้านใน

AppScaffold {
    val pagerState = rememberPagerState(pageCount = { 10 })

    HorizontalPagerScaffold(pagerState = pagerState) {
        HorizontalPager(
            state = pagerState,
            flingBehavior =
                PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                    state = pagerState
                ),
        ) { page ->
            AnimatedPage(pageIndex = page, pagerState = pagerState) {
                val columnState = rememberTransformingLazyColumnState()
                val transformationSpec = rememberTransformationSpec()

                ScreenScaffold(
                    scrollState = columnState,
                ) { contentPadding ->
                    TransformingLazyColumn(
                        state = columnState,
                        contentPadding = contentPadding,
                    ) {
                        item {
                            ListHeader(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .transformedHeight(this, transformationSpec)
                                    .minimumVerticalContentPadding(
                                        ListHeaderDefaults.minimumTopListContentPadding
                                    ),
                                transformation = SurfaceTransformation(transformationSpec),
                            ) {
                                Text(text = "Pager sample")
                            }
                        }
                        item {
                            Card(
                                modifier = Modifier
                                    .fillMaxWidth()
                                    .transformedHeight(this, transformationSpec)
                                    .minimumVerticalContentPadding(
                                        CardDefaults.minimumVerticalListContentPadding
                                    ),
                                transformation = SurfaceTransformation(transformationSpec),
                            ) {
                                if (page == 0) {
                                    Text(text = "Page #$page. Swipe right")
                                } else {
                                    Text(text = "Page #$page. Swipe left and right")
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

ใช้เพจเจอร์แนวตั้ง

เครื่องมือเปลี่ยนหน้าแนวตั้งช่วยให้ผู้ใช้ปัดขึ้นและลงระหว่างหน้าต่างๆ หรือเลื่อนผ่านหน้าต่างๆ ได้ โดยใช้ปุ่มด้านข้างแบบหมุนหรือขอบหน้าปัดของนาฬิกา

VerticalPager ช่วยให้เปิดใช้การสแนปหน้าแบบหมุนได้โดยค่าเริ่มต้น (RotaryScrollableDefaults.snapBehavior(state)) ซึ่งแตกต่างจาก HorizontalPager ส่ง PagerScaffoldDefaults.snapWithSpringFlingBehavior ไปยัง flingBehavior และห่อหุ้มแต่ละหน้าใน AnimatedPage และ ScreenScaffold

@Composable
fun VerticalPagerScaffoldSample() {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 10 })

        VerticalPagerScaffold(pagerState = pagerState) {
            VerticalPager(
                state = pagerState,
                flingBehavior =
                    PagerScaffoldDefaults.snapWithSpringFlingBehavior(
                        state = pagerState
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        Column(
                            modifier = Modifier.fillMaxSize(),
                            horizontalAlignment = Alignment.CenterHorizontally,
                            verticalArrangement = Arrangement.Center,
                        ) {
                            Text(text = "Page #$page")
                            Spacer(modifier = Modifier.height(8.dp))
                            Text(text = "Swipe up and down")
                        }
                    }
                }
            }
        }
    }
}

ปรับแต่งลักษณะการทำงานของเพจเจอร์

คุณปรับแต่งความไวในการสแนปของ HorizontalPagerScaffold และ VerticalPagerScaffold ให้ตรงกับความต้องการในการโต้ตอบของแอปได้

ปรับความไวในการสแนปสำหรับการออกกำลังกาย

ในระหว่างการออกกำลังกายหรือกิจกรรมที่มีการเคลื่อนไหวสูงซึ่งการควบคุมการเคลื่อนไหวขั้นต้นของผู้ใช้ ถูกจำกัด การสัมผัสโดยไม่ตั้งใจหรือการหมุนเม็ดมะยมเล็กน้อยอาจเปลี่ยนหน้าโดยไม่ตั้งใจ สำหรับหน้าจอเหล่านี้ ให้กำหนดค่าเพจเจอร์ที่มีความไวในการสแนปต่ำ

  • ปัด PagerDefaults.snapFlingBehavior ด้วย maxFlingPages = 0 และ snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold จึงต้องลากข้ามส่วนที่ใหญ่กว่าของหน้าจอเพื่อ เปลี่ยนหน้า
  • หากเปิดใช้การสแนปหน้าแบบหมุน ให้ตั้งค่า snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivityใน RotaryScrollableDefaults.snapBehavior เพื่อกำหนดให้ต้องหมุนมากขึ้นก่อน สแนปไปยังหน้าถัดไป
@Composable
fun HorizontalPagerScaffoldWithLowSensitivitySample(navigateBack: () -> Unit) {
    AppScaffold {
        val pagerState = rememberPagerState(pageCount = { 3 })

        HorizontalPagerScaffold(pagerState = pagerState) {
            HorizontalPager(
                state = pagerState,
                flingBehavior =
                    PagerDefaults.snapFlingBehavior(
                        state = pagerState,
                        maxFlingPages = 0,
                        snapPositionalThreshold =
                            PagerScaffoldDefaults.LowSnapPositionalThreshold,
                    ),
                rotaryScrollableBehavior =
                    RotaryScrollableDefaults.snapBehavior(
                        pagerState = pagerState,
                        snapSensitivity =
                            RotaryScrollableDefaults.LowSnapSensitivity,
                    ),
            ) { page ->
                AnimatedPage(pageIndex = page, pagerState = pagerState) {
                    ScreenScaffold {
                        // Page content
                    }
                }
            }
        }
    }
}

ดูตัวอย่างการติดตั้งใช้งานทั้งหมดได้ที่ Pager.kt ในที่เก็บ android/snippets และ PagerScaffoldSample.kt ในที่เก็บ AndroidX