Phân trang bằng Compose cho Wear OS

Phân trang cho phép người dùng vuốt theo chiều ngang hoặc chiều dọc giữa các trang riêng biệt, toàn màn hình trên thiết bị Wear OS. Các trường hợp sử dụng phổ biến bao gồm vuốt giữa các chỉ số tập luyện và chế độ điều khiển nội dung nghe nhìn trong một ứng dụng tập thể dục, hoặc chuyển qua các luồng nhiều trang.

Trong Compose cho Wear OS Material 3, HorizontalPagerScaffoldVerticalPagerScaffold sẽ điều phối bố cục trình phân trang, tự động định vị chỉ báo trang và quản lý các hiệu ứng chuyển đổi bằng TimeText. Mỗi trang được bao bọc trong một thành phần kết hợp AnimatedPage, áp dụng hiệu ứng thu phóng, biến đổi góc bo tròn và hiệu ứng lớp phủ khi các trang chuyển đổi trên màn hình tròn.

Ảnh động sau đây cho thấy cách HorizontalPagerScaffoldAnimatedPage điều chỉnh tỷ lệ và tạo hiệu ứng cho các trang trong quá trình vuốt ngang:

Hệ phân cấp giàn giáo để phân trang

Khi thiết kế bố cục màn hình phân trang trong Material 3 – cho dù vuốt sang trái hay phải (HorizontalPager) hoặc lên hay xuống (VerticalPager) – hãy sử dụng hệ phân cấp thành phần sau đây từ vùng chứa bên ngoài đến nội dung bên trong:

  1. AppScaffold: Vùng chứa ngoài cùng ở gốc của ứng dụng (chỉ sử dụng một vùng chứa cho mỗi ứng dụng). Thao tác này sẽ cố định lớp phủ TimeText chung để đồng hồ vẫn đứng yên ở đầu màn hình trong quá trình chuyển đổi trang và cử chỉ vuốt để đóng.
  2. HorizontalPagerScaffold hoặc VerticalPagerScaffold: Đặt bên trong AppScaffold ở cấp trình phân trang. Thành phần này điều phối các hiệu ứng chuyển đổi giữa TimeTextHorizontalPageIndicator hoặc VerticalPageIndicator.
  3. HorizontalPager hoặc VerticalPager: Vùng chứa trình phân trang cơ sở quản lý cử chỉ vuốt, vật lý hất và thao tác nhập xoay bằng cách sử dụng PagerState dùng chung.
  4. AnimatedPage: Được đặt bên trong hàm lambda nội dung trang của trình phân trang. Thành phần này bao bọc từng trang riêng lẻ để áp dụng các hiệu ứng chuyển đổi Material 3 (hiệu ứng thu phóng và lớp phủ) dựa trên độ lệch của trang trong PagerState.
  5. ScreenScaffold: Được đặt bên trong AnimatedPage cho từng trang riêng lẻ. Vì mỗi trang có thể chứa danh sách riêng có thể cuộn theo chiều dọc (chẳng hạn như TransformingLazyColumn) hoặc EdgeButton riêng, việc lồng ScreenScaffold bên trong mỗi trang đảm bảo rằng các chỉ báo cuộn theo chiều dọc, nút cạnh và contentPadding thuộc về trang cụ thể đó và chuyển động mượt mà cùng với trang.
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)

Triển khai chế độ phân trang ngang

Để triển khai một trình phân trang ngang trong Material 3, hãy lồng HorizontalPagerScaffold, HorizontalPager, AnimatedPageScreenScaffold bên trong AppScaffold của ứng dụng.

Sử dụng PagerScaffoldDefaults.snapWithSpringFlingBehavior cho flingBehavior để áp dụng vật lý chuyển động lò xo Material 3 và tính năng chụp nhanh trang thích ứng (HighSnapPositionalThreshold). Theo mặc định, HorizontalPager sẽ tắt tính năng cuộn trang bằng thao tác xoay (rotaryScrollableBehavior = null), cho phép thao tác xoay núm vặn hoặc viền để cuộn danh sách dọc (chẳng hạn như TransformingLazyColumn) bên trong trang đang hoạt động.

Mẫu sau đây minh hoạ chế độ thiết lập HorizontalPagerScaffold hoàn chỉnh:

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

Nếu một trang bên trong trình phân trang ngang của bạn chứa TransformingLazyColumn, hãy tạo một rememberTransformingLazyColumnState() riêng biệt trong phạm vi của trang đó và truyền nó đến ScreenScaffoldTransformingLazyColumn bên trong:

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

Triển khai chế độ phân trang dọc

Trình phân trang dọc cho phép người dùng vuốt lên và xuống giữa các trang hoặc chuyển qua các trang bằng cách sử dụng nút bên xoay hoặc viền xoay của đồng hồ.

Không giống như HorizontalPager, VerticalPager cho phép chế độ chụp nhanh trang xoay theo mặc định (RotaryScrollableDefaults.snapBehavior(state)). Truyền PagerScaffoldDefaults.snapWithSpringFlingBehavior đến flingBehavior và bao bọc từng trang trong AnimatedPageScreenScaffold:

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

Tuỳ chỉnh hành vi của trình phân trang

Bạn có thể tuỳ chỉnh độ nhạy của tính năng căn chỉnh nhanh HorizontalPagerScaffoldVerticalPagerScaffold cho phù hợp với nhu cầu tương tác của ứng dụng.

Điều chỉnh độ nhạy của tính năng chụp nhanh cho bài tập

Trong quá trình tập luyện hoặc các hoạt động có nhiều chuyển động mà khả năng kiểm soát vận động thô của người dùng bị hạn chế, các thao tác chạm vô tình hoặc xoay núm vặn nhẹ có thể vô tình chuyển đổi trang. Đối với những màn hình này, hãy định cấu hình bộ phân trang với độ nhạy thấp khi di chuyển nhanh:

  • Truyền PagerDefaults.snapFlingBehavior bằng maxFlingPages = 0snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold, do đó, bạn phải kéo một cách có chủ ý trên một phần lớn hơn của màn hình để chuyển trang.
  • Nếu bạn bật tính năng chụp nhanh trang xoay, hãy đặt snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity trong RotaryScrollableDefaults.snapBehavior để yêu cầu xoay nhiều hơn trước khi chụp nhanh sang trang tiếp theo.
@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
                    }
                }
            }
        }
    }
}

Để xem các cách triển khai mẫu đầy đủ, hãy xem Pager.kt trong kho lưu trữ android/snippetsPagerScaffoldSample.kt trong kho lưu trữ AndroidX.