使用 Compose for Wear OS 分頁

分頁功能可讓使用者在 Wear OS 裝置上,以水平或垂直方向滑動,切換不同的全螢幕頁面。常見用途包括在運動應用程式中滑動切換運動指標和媒體控制項,或是逐步完成多頁面流程。

在 Wear OS Material 3 的 Compose 中,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 中實作橫向頁面控制項,請在應用程式的 AppScaffold 中,巢狀內嵌 HorizontalPagerScaffoldHorizontalPagerAnimatedPageScreenScaffold

使用 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(),並傳遞至內部 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),因此必須在螢幕較大的部分上刻意拖曳,才能翻頁。
  • 如果啟用旋轉頁面快速切換功能,請在 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
                    }
                }
            }
        }
    }
}

如需完整的實作範例,請參閱 android/snippets 存放區中的 Pager.kt,以及 AndroidX 存放區中的 PagerScaffoldSample.kt