Paginação com o Compose para Wear OS

A paginação permite que os usuários deslizem na horizontal ou vertical entre páginas distintas em tela cheia nos dispositivos Wear OS. Casos de uso comuns incluem deslizar entre métricas de treino e controles de mídia em um app de exercícios ou navegar por fluxos de várias páginas.

No Compose para Wear OS Material 3, HorizontalPagerScaffold e VerticalPagerScaffold coordenam o layout do pager, posicionando automaticamente o indicador de página e gerenciando transições com TimeText. Cada página é encapsulada em um elemento combinável AnimatedPage, que aplica escalonamento, transformação de cantos arredondados e efeitos de tela escura à medida que as páginas transitam pela tela redonda.

A animação a seguir mostra como HorizontalPagerScaffold e AnimatedPage dimensionam e animam páginas durante deslizes horizontais:

Estrutura de hierarquia para paginação

Ao criar layouts de tela paginados no Material 3, seja deslizando para a esquerda ou direita (HorizontalPager) ou para cima ou para baixo (VerticalPager), use a seguinte hierarquia de componentes do contêiner externo para o conteúdo interno:

  1. AppScaffold: o contêiner mais externo na raiz do seu app (use apenas um por app). Ele ancora a sobreposição global TimeText para que o relógio permaneça parado na parte de cima da tela durante as transições de página e os gestos de deslizar para dispensar.
  2. HorizontalPagerScaffold ou VerticalPagerScaffold: colocado dentro de AppScaffold no nível do pager. Ele coordena transições entre TimeText e HorizontalPageIndicator ou VerticalPageIndicator.
  3. HorizontalPager ou VerticalPager: o contêiner do pager de base que gerencia gestos de deslizar, física de movimento rápido e entrada por seletor giratório usando um PagerState compartilhado.
  4. AnimatedPage: colocado dentro da lambda de conteúdo da página do pager. Ele encapsula cada página individual para aplicar animações de transição do Material Design 3 (efeitos de escalonamento e scrim) com base no deslocamento da página em PagerState.
  5. ScreenScaffold: colocado dentro de AnimatedPage para cada página individual. Como cada página pode conter uma lista rolável vertical (como um TransformingLazyColumn) ou um EdgeButton, o aninhamento de ScreenScaffold em cada página garante que indicadores de rolagem vertical, botões de borda e contentPadding pertençam a essa página específica e sejam animados de maneira suave com ela.
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)

Implementar um pager horizontal

Para implementar um pager horizontal no Material 3, aninhe HorizontalPagerScaffold, HorizontalPager, AnimatedPage e ScreenScaffold dentro do AppScaffold do seu app.

Use PagerScaffoldDefaults.snapWithSpringFlingBehavior para flingBehavior e aplique a física de movimento de mola do Material 3 e o ajuste responsivo da página (HighSnapPositionalThreshold). Por padrão, HorizontalPager desativa a rolagem da página com o seletor giratório (rotaryScrollableBehavior = null), permitindo que a coroa ou o bisel giratório rolem listas verticais (como um TransformingLazyColumn) dentro da página ativa.

O exemplo a seguir demonstra uma configuração completa de 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") }
                            }
                        }
                    }
                }
            }
        }
    }
}

Se uma página dentro do pager horizontal tiver um TransformingLazyColumn, crie um rememberTransformingLazyColumnState() separado dentro do escopo dessa página e transmita-o para o ScreenScaffold e o TransformingLazyColumn internos:

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

Implementar um pager vertical

Com um pager vertical, os usuários podem deslizar para cima e para baixo entre as páginas ou navegar por elas usando o botão lateral giratório ou o aro do relógio.

Ao contrário de HorizontalPager, o VerticalPager ativa o ajuste rotativo de página por padrão (RotaryScrollableDefaults.snapBehavior(state)). Transmita PagerScaffoldDefaults.snapWithSpringFlingBehavior para flingBehavior e encapsule cada página em AnimatedPage e 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")
                        }
                    }
                }
            }
        }
    }
}

Personalizar o comportamento do pager

É possível personalizar a sensibilidade de ajuste de HorizontalPagerScaffold e VerticalPagerScaffold para corresponder às necessidades de interação do seu app.

Ajustar a sensibilidade do ajuste para treinos

Durante treinos ou atividades de alta intensidade em que o controle motor grosso do usuário é limitado, toques acidentais ou rotações leves da coroa podem mudar as páginas sem querer. Para essas telas, configure o pager com baixa sensibilidade de ajuste:

  • Transmita PagerDefaults.snapFlingBehavior com maxFlingPages = 0 e snapPositionalThreshold = PagerScaffoldDefaults.LowSnapPositionalThreshold para que seja necessário arrastar deliberadamente uma parte maior da tela para virar a página.
  • Se você ativar o ajuste de página rotativo, defina snapSensitivity = RotaryScrollableDefaults.LowSnapSensitivity em RotaryScrollableDefaults.snapBehavior para exigir mais rotação antes de ajustar para a próxima página.
@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
                    }
                }
            }
        }
    }
}

Para conferir implementações de amostra completas, consulte Pager.kt no repositório android/snippets e PagerScaffoldSample.kt no repositório do AndroidX.