使用 Compose for Wear OS 进行分页

分页功能可让用户在 Wear OS 设备上以水平或垂直方式在不同的全屏页面之间滑动。常见用例包括在锻炼应用中滑动切换锻炼指标和媒体控件,或逐步完成多页面流程。

在适用于 Wear OS 的 Compose Material 3 中,HorizontalPagerScaffoldVerticalPagerScaffold 可协调分页器布局,自动定位页面指示器并使用 TimeText 管理过渡效果。每个页面都封装在 AnimatedPage 可组合项中,该可组合项会在页面在圆形显示屏上切换时应用缩放、圆角变形和幕帘效果。

以下动画展示了 HorizontalPagerScaffoldAnimatedPage 在水平滑动期间如何缩放和动画显示页面:

分页的支架层次结构

在 Material 3 中设计分页屏幕布局时,无论是向左或向右滑动 (HorizontalPager) 还是向上或向下滑动 (VerticalPager),都应使用以下组件层次结构(从外部容器到内部内容):

  1. AppScaffold:应用根目录中的最外层容器(每个应用只能使用一个)。它会锚定全局 TimeText 叠加层,以便在页面转换和滑动关闭手势期间,时钟保持静止在屏幕顶部。
  2. HorizontalPagerScaffoldVerticalPagerScaffold:放置在分页器级别的 AppScaffold 内。它协调 TimeTextHorizontalPageIndicatorVerticalPageIndicator 之间的转换。
  3. HorizontalPagerVerticalPager:基础分页容器,使用共享的 PagerState 管理滑动操作、快速滑动物理特性和旋转输入贴靠。
  4. AnimatedPage:放置在分页器的页面内容 lambda 中。它会封装每个单独的页面,以根据页面在 PagerState 中的偏移量应用 Material 3 过渡动画(缩放和幕布效果)。
  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 中实现水平分页器,请将 HorizontalPagerScaffoldHorizontalPagerAnimatedPageScreenScaffold 嵌套在应用的 AppScaffold 中。

使用 flingBehaviorPagerScaffoldDefaults.snapWithSpringFlingBehavior 可应用 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(),并将其传递给内部 ScreenScaffoldTransformingLazyColumn

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

实现纵向页面控制项

借助竖向分页器,用户可以上下滑动来浏览各个页面,也可以使用手表的旋转侧边按钮或表圈来逐步浏览各个页面。

HorizontalPager 不同,VerticalPager 默认启用旋转页面贴靠 (RotaryScrollableDefaults.snapBehavior(state))。将 PagerScaffoldDefaults.snapWithSpringFlingBehavior 传递给 flingBehavior,并将每个页面封装在 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")
                        }
                    }
                }
            }
        }
    }
}

自定义分页器行为

您可以自定义 HorizontalPagerScaffoldVerticalPagerScaffold 的贴靠灵敏度,以满足应用的互动需求。

调整锻炼时的贴靠灵敏度

在锻炼或大幅度运动期间,用户的粗略运动控制能力有限,意外触碰或轻微旋转表冠可能会无意中切换页面。对于这些屏幕,请将寻呼机的贴靠灵敏度配置为低:

  • 传递 PagerDefaults.snapFlingBehavior,并使用 maxFlingPages = 0snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold,因此需要有意地在屏幕的较大部分区域进行拖动才能翻页。
  • 如果您启用旋转页面贴靠,请在 RotaryScrollableDefaults.snapBehavior 中设置 snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity,以要求在贴靠到下一页之前进行更多旋转。
@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
                    }
                }
            }
        }
    }
}

如需查看完整的示例实现,请参阅 android/snippets 代码库中的 Pager.kt 和 AndroidX 代码库中的 PagerScaffoldSample.kt