Pager no Compose

Para navegar pelo conteúdo da esquerda e da direita ou para cima e para baixo, use os elementos combináveis HorizontalPager e VerticalPager, respectivamente. Esses elementos combináveis têm funções semelhantes a ViewPager no sistema de visualização. Por padrão, HorizontalPager ocupa toda a largura da tela, VerticalPager ocupa toda a altura total e os pagers só rolam uma página por vez. Todos esses padrões podem ser configurados.

HorizontalPager

Para criar um pager que rola horizontalmente para a esquerda e para a direita, use HorizontalPager:

Figura 1. Demonstração de HorizontalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

VerticalPager

Para criar um pager que rola para cima e para baixo, use VerticalPager:

Figura 2. Demonstração de VerticalPager

// Display 10 items
val pagerState = rememberPagerState(pageCount = {
    10
})
VerticalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier.fillMaxWidth()
    )
}

Criação lenta

As páginas em HorizontalPager e VerticalPager são compostas lentamente e dispostas quando necessário. À medida que o usuário rola as páginas, o elemento combinável remove aquelas que não são mais necessárias.

Carregar mais páginas fora da tela

Por padrão, o paginador carrega apenas as páginas visíveis na tela. Para carregar mais páginas fora da tela, defina beyondBoundsPageCount como um valor maior que zero.

Rolar até um item no pager

Para rolar até uma página específica no paginador, crie um objeto PagerState usando rememberPagerState() e transmita-o como o parâmetro state ao pager. É possível chamar PagerState#scrollToPage() nesse estado, dentro de um CoroutineScope:

val pagerState = rememberPagerState(pageCount = {
    10
})
HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.scrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

Se você quiser animar a página, use a função PagerState#animateScrollToPage():

val pagerState = rememberPagerState(pageCount = {
    10
})

HorizontalPager(state = pagerState) { page ->
    // Our page content
    Text(
        text = "Page: $page",
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
    )
}

// scroll to page
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
    coroutineScope.launch {
        // Call scroll to on pagerState
        pagerState.animateScrollToPage(5)
    }
}, modifier = Modifier.align(Alignment.BottomCenter)) {
    Text("Jump to Page 5")
}

Receber notificações sobre alterações de estado da página

PagerState tem três propriedades com informações sobre páginas: currentPage, settledPage e targetPage.

  • currentPage: a página mais próxima da posição de ajuste. Por padrão, a posição de ajuste está no início do layout.
  • settledPage: o número da página quando nenhuma animação ou rolagem está em execução. Isso é diferente da propriedade currentPage, em que o currentPage é atualizado imediatamente se a página estiver próxima o suficiente da posição de ajuste, mas settledPage permanece a mesma até que todas as animações terminem de ser executadas.
  • targetPage: a posição de parada proposta para um movimento de rolagem.

Use a função snapshotFlow para observar as mudanças nessas variáveis e reagir a elas. Por exemplo, para enviar um evento de análise em cada mudança de página, faça o seguinte:

val pagerState = rememberPagerState(pageCount = {
    10
})

LaunchedEffect(pagerState) {
    // Collect from the a snapshotFlow reading the currentPage
    snapshotFlow { pagerState.currentPage }.collect { page ->
        // Do something with each page change, for example:
        // viewModel.sendPageSelectedEvent(page)
        Log.d("Page change", "Page changed to $page")
    }
}

VerticalPager(
    state = pagerState,
) { page ->
    Text(text = "Page: $page")
}

Adicionar um indicador de página

Para adicionar um indicador a uma página, use o objeto PagerState para ver informações sobre qual página foi selecionada do número de páginas e desenhe seu indicador personalizado.

Por exemplo, se você quiser um indicador de círculo simples, repita o número de círculos e mude a cor deles com base na seleção da página, usando pagerState.currentPage:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    modifier = Modifier.fillMaxSize()
) { page ->
    // Our page content
    Text(
        text = "Page: $page",
    )
}
Row(
    Modifier
        .wrapContentHeight()
        .fillMaxWidth()
        .align(Alignment.BottomCenter)
        .padding(bottom = 8.dp),
    horizontalArrangement = Arrangement.Center
) {
    repeat(pagerState.pageCount) { iteration ->
        val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray
        Box(
            modifier = Modifier
                .padding(2.dp)
                .clip(CircleShape)
                .background(color)
                .size(16.dp)
        )
    }
}

Pager mostrando um indicador de círculo abaixo do conteúdo
Figura 3. Pager mostrando um indicador de círculo abaixo do conteúdo.

Aplicar efeitos de rolagem de itens ao conteúdo

Um caso de uso comum é aplicar a posição de rolagem para aplicar efeitos aos itens de paginador. Para descobrir a distância entre uma página e a página selecionada, use PagerState.currentPageOffsetFraction. Você pode aplicar efeitos de transformação ao seu conteúdo com base na distância da página selecionada.

Figura 4. Como aplicar transformações ao conteúdo do Pager

Por exemplo, para ajustar a opacidade dos itens com base na distância entre eles e o centro, mude a alpha usando Modifier.graphicsLayer em um item dentro do paginador:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(state = pagerState) { page ->
    Card(
        Modifier
            .size(200.dp)
            .graphicsLayer {
                // Calculate the absolute offset for the current page from the
                // scroll position. We use the absolute value which allows us to mirror
                // any effects for both directions
                val pageOffset = (
                    (pagerState.currentPage - page) + pagerState
                        .currentPageOffsetFraction
                    ).absoluteValue

                // We animate the alpha, between 50% and 100%
                alpha = lerp(
                    start = 0.5f,
                    stop = 1f,
                    fraction = 1f - pageOffset.coerceIn(0f, 1f)
                )
            }
    ) {
        // Card content
    }
}

Tamanhos de página personalizados

Por padrão, HorizontalPager e VerticalPager ocupam a largura total ou a altura total, respectivamente. É possível definir a variável pageSize para ter um Fixed, Fill (padrão) ou um cálculo de tamanho personalizado.

Por exemplo, para definir uma página com largura fixa de 100.dp:

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    pageSize = PageSize.Fixed(100.dp)
) { page ->
    // page content
}

Para dimensionar as páginas com base no tamanho da janela de visualização, use um cálculo personalizado. Crie um objeto PageSize personalizado e divida o availableSpace por três, considerando o espaçamento entre os itens:

private val threePagesPerViewport = object : PageSize {
    override fun Density.calculateMainAxisPageSize(
        availableSpace: Int,
        pageSpacing: Int
    ): Int {
        return (availableSpace - 2 * pageSpacing) / 3
    }
}

Padding de conteúdo

HorizontalPager e VerticalPager são compatíveis com a alteração do padding do conteúdo, o que permite influenciar o tamanho e o alinhamento máximos das páginas.

Por exemplo, definir o padding start alinha as páginas no final:

Pager com padding inicial mostrando o conteúdo alinhado no fim

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(start = 64.dp),
) { page ->
    // page content
}

Definir o padding start e end como o mesmo valor centraliza o item na horizontal:

Pager com padding de início e fim mostrando o conteúdo centralizado

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(horizontal = 32.dp),
) { page ->
    // page content
}

Definir o padding end alinha as páginas em direção ao início:

Pager com padding de início e fim mostrando o conteúdo alinhado ao início

val pagerState = rememberPagerState(pageCount = {
    4
})
HorizontalPager(
    state = pagerState,
    contentPadding = PaddingValues(end = 64.dp),
) { page ->
    // page content
}

Você pode definir os valores top e bottom para ter efeitos semelhantes para VerticalPager. O valor 32.dp é usado aqui apenas como exemplo. Você pode definir qualquer uma das dimensões de padding com qualquer valor.

Personalizar o comportamento de rolagem

Os elementos combináveis HorizontalPager e VerticalPager padrão especificam como os gestos de rolagem funcionam com o paginador. No entanto, é possível personalizar e mudar os padrões, como pagerSnapDistance ou flingBehaviour.

Ajustar distância

Por padrão, HorizontalPager e VerticalPager definem o número máximo de páginas que um gesto de rolagem pode rolar para uma página por vez. Para mudar isso, defina pagerSnapDistance em flingBehavior:

val pagerState = rememberPagerState(pageCount = { 10 })

val fling = PagerDefaults.flingBehavior(
    state = pagerState,
    pagerSnapDistance = PagerSnapDistance.atMost(10)
)

Column(modifier = Modifier.fillMaxSize()) {
    HorizontalPager(
        state = pagerState,
        pageSize = PageSize.Fixed(200.dp),
        beyondBoundsPageCount = 10,
        flingBehavior = fling
    ) {
        PagerSampleItem(page = it)
    }
}