Постраничная навигация с помощью Compose для Wear OS

Функция постраничной навигации позволяет пользователям переключаться между отдельными полноэкранными страницами на устройствах Wear OS с помощью свайпов по горизонтали или вертикали. Типичные сценарии использования включают переключение между показателями тренировки и элементами управления мультимедиа в приложении для тренировок, а также пошаговое перемещение по многостраничным сценариям.

В Compose for Wear OS Material 3, HorizontalPagerScaffold и VerticalPagerScaffold координируют компоновку пейджера, автоматически позиционируя индикатор страницы и управляя переходами с помощью TimeText . Каждая страница заключена в компонент AnimatedPage , который применяет масштабирование, изменение скругленных углов и эффекты затемнения при переходе страниц по круглому экрану.

Следующая анимация демонстрирует, как HorizontalPagerScaffold и AnimatedPage масштабируют и анимируют страницы при горизонтальном свайпе:

Иерархическая структура для постраничной навигации

При разработке макетов экранов с постраничной разбивкой в ​​Material 3 — независимо от того, используется ли свайп влево или вправо ( HorizontalPager ) или вверх или вниз ( VerticalPager ) — используйте следующую иерархию компонентов от внешнего контейнера к внутреннему содержимому:

  1. AppScaffold : Самый внешний контейнер в корне вашего приложения (используйте только один на приложение). Он закрепляет глобальное наложение TimeText , благодаря чему часы остаются неподвижными в верхней части экрана во время переходов между страницами и жестов смахивания для закрытия.
  2. HorizontalPagerScaffold или VerticalPagerScaffold : Размещается внутри AppScaffold на уровне пейджера. Координирует переходы между TimeText и HorizontalPageIndicator или VerticalPageIndicator .
  3. HorizontalPager или VerticalPager : базовый контейнер пейджера, который управляет жестами смахивания, физикой броска и привязкой к вращающемуся вводу с помощью общего PagerState .
  4. AnimatedPage : Размещается внутри лямбда-функции содержимого страницы пейджера. Она оборачивает каждую отдельную страницу, применяя анимации перехода Material 3 (масштабирование и эффекты затемнения) в зависимости от смещения страницы в PagerState .
  5. ScreenScaffold : Размещается внутри AnimatedPage для каждой отдельной страницы. Поскольку каждая страница может содержать собственный список с вертикальной прокруткой (например, TransformingLazyColumn ) или собственную EdgeButton , вложенность ScreenScaffold внутри каждой страницы гарантирует, что индикаторы вертикальной прокрутки, кнопки EdgeButton и 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")
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Внедрить вертикальный пейджер

Вертикальный пейджер позволяет пользователям переключаться между страницами с помощью свайпов вверх и вниз или переходить между ними пошагово, используя вращающуюся боковую кнопку или безель часов.

В отличие от HorizontalPager , VerticalPager по умолчанию включает привязку страниц при вращении ( RotaryScrollableDefaults.snapBehavior(state) ). Передайте 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.