Paging mit Compose für Wear OS

Mit der Paging-Funktion können Nutzer auf Wear OS-Geräten horizontal oder vertikal zwischen verschiedenen Vollbildseiten wischen. Häufige Anwendungsfälle sind das Wischen zwischen Trainingsmesswerten und Mediensteuerungen in einer Trainings-App oder das Durchlaufen mehrseitiger Abläufe.

In Compose für Wear OS Material 3 koordinieren HorizontalPagerScaffold und VerticalPagerScaffold das Pager-Layout. Der Seitenindikator wird automatisch positioniert und Übergänge werden mit TimeText verwaltet. Jede Seite ist in ein AnimatedPage-Composable eingeschlossen, das beim Übergang von Seiten auf dem runden Display Skalierung, Morphing von abgerundeten Ecken und Scrim-Effekte anwendet.

Die folgende Animation zeigt, wie HorizontalPagerScaffold und AnimatedPage Seiten bei horizontalen Wischbewegungen skalieren und animieren:

Gerüst für die Paginierung erstellen

Wenn Sie in Material 3 Layouts mit Seitennavigation entwerfen – unabhängig davon, ob Sie nach links oder rechts (HorizontalPager) oder nach oben oder unten (VerticalPager) wischen –, verwenden Sie die folgende Komponentenstruktur vom äußeren Container bis zum inneren Inhalt:

  1. AppScaffold: Der äußerste Container im Stammverzeichnis Ihrer App (nur einer pro App). Dadurch wird das globale TimeText-Overlay fixiert, sodass die Uhr bei Seitenübergängen und Wischgesten zum Schließen oben auf dem Display bleibt.
  2. HorizontalPagerScaffold oder VerticalPagerScaffold: Wird auf Pager-Ebene in AppScaffold platziert. Sie koordiniert Übergänge zwischen TimeText und HorizontalPageIndicator oder VerticalPageIndicator.
  3. HorizontalPager oder VerticalPager: Der Foundation-Pager-Container, der Wischbewegungen, Fling-Physik und das Einrasten bei der Dreheingabe mithilfe eines gemeinsamen PagerState verwaltet.
  4. AnimatedPage: Wird in der Lambda-Funktion für den Seiteninhalt des Pagers platziert. Sie umschließt jede einzelne Seite, um basierend auf dem Offset der Seite in PagerState Material 3-Übergangsanimationen (Skalierungs- und Scrim-Effekte) anzuwenden.
  5. ScreenScaffold: Wird innerhalb von AnimatedPage für jede einzelne Seite platziert. Da jede Seite eine eigene vertikal scrollbare Liste (z. B. eine TransformingLazyColumn) oder ein eigenes EdgeButton enthalten kann, wird durch das Einbetten von ScreenScaffold auf jeder Seite dafür gesorgt, dass vertikale Scrollindikatoren, Schaltflächen am Rand und contentPadding zu dieser Seite gehören und sich reibungslos mit ihr animieren lassen.
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)

Horizontalen Pager implementieren

Wenn Sie einen horizontalen Pager in Material 3 implementieren möchten, verschachteln Sie HorizontalPagerScaffold, HorizontalPager, AnimatedPage und ScreenScaffold in der AppScaffold Ihrer App.

Verwende PagerScaffoldDefaults.snapWithSpringFlingBehavior für flingBehavior, um die Physik der Material 3-Federbewegung und das responsive Andocken von Seiten (HighSnapPositionalThreshold) anzuwenden. Standardmäßig wird durch HorizontalPager das Scrollen von Seiten mit der drehbaren Lünette (rotaryScrollableBehavior = null) deaktiviert. So kann durch Drehen der Krone oder Lünette in vertikalen Listen (z. B. in einem TransformingLazyColumn) auf der aktiven Seite gescrollt werden.

Im folgenden Beispiel wird eine vollständige HorizontalPagerScaffold-Einrichtung veranschaulicht:

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

Wenn eine Seite in Ihrem horizontalen Pager ein TransformingLazyColumn enthält, erstellen Sie ein separates rememberTransformingLazyColumnState() innerhalb des Bereichs dieser Seite und übergeben Sie es an das innere ScreenScaffold und 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")
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Vertikale Seite implementieren

Mit einem vertikalen Pager können Nutzer durch Wischen nach oben und unten zwischen Seiten wechseln oder die drehbare Seitentaste oder Lünette der Smartwatch verwenden, um die Seiten durchzugehen.

Im Gegensatz zu HorizontalPager wird bei VerticalPager standardmäßig das Einrasten von Seiten beim Drehen aktiviert (RotaryScrollableDefaults.snapBehavior(state)). Übergeben Sie PagerScaffoldDefaults.snapWithSpringFlingBehavior an flingBehavior und umschließen Sie jede Seite mit AnimatedPage und 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")
                        }
                    }
                }
            }
        }
    }
}

Pager-Verhalten anpassen

Sie können die Einrastempfindlichkeit von HorizontalPagerScaffold und VerticalPagerScaffold an die Interaktionsanforderungen Ihrer App anpassen.

Empfindlichkeit für das Einrasten bei Trainings anpassen

Bei Trainings oder Aktivitäten mit starken Bewegungen, bei denen die grobmotorische Kontrolle des Nutzers eingeschränkt ist, kann es durch versehentliche Berührungen oder leichte Kronendrehungen zu einem unbeabsichtigten Seitenwechsel kommen. Konfigurieren Sie für diese Bildschirme den Pager mit einer geringen Einrastempfindlichkeit:

  • Übergeben Sie PagerDefaults.snapFlingBehavior mit maxFlingPages = 0 und snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold. Zum Umblättern ist also ein bewusstes Ziehen über einen größeren Teil des Displays erforderlich.
  • Wenn Sie das Einrasten von Seiten beim Drehen aktivieren, legen Sie snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity in RotaryScrollableDefaults.snapBehavior fest, um mehr Drehung zu erfordern, bevor zur nächsten Seite gewechselt wird.
@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
                    }
                }
            }
        }
    }
}

Vollständige Beispielimplementierungen finden Sie unter Pager.kt im android/snippets-Repository und unter PagerScaffoldSample.kt im AndroidX-Repository.