Rolagem

Modificadores de rolagem

Os modificadores verticalScroll e horizontalScroll oferecem a forma mais simples de permitir que o usuário role um elemento quando os limites do conteúdo são maiores que as restrições de tamanho máximo. Com os modificadores verticalScroll e horizontalScroll, não é necessário transladar nem deslocar o conteúdo.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Uma lista vertical simples respondendo a gestos de rolagem
Figura 1. Uma lista vertical simples que responde a gestos de rolagem.

O ScrollState permite mudar a posição de rolagem ou descobrir o estado atual. Para criá-lo com parâmetros padrão, use rememberScrollState().

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Modificador de área rolável

O modificador scrollableArea é um elemento fundamental para criar contêineres roláveis personalizados. Ele oferece uma abstração de nível mais alto sobre o modificador scrollable, processando requisitos comuns, como interpretação do delta de gestos, corte de conteúdo e efeitos de rolagem excessiva.

Embora scrollableArea seja usado para implementações personalizadas, geralmente é melhor preferir soluções prontas, como verticalScroll, horizontalScroll ou elementos combináveis, como LazyColumn, para listas de rolagem padrão. Esses componentes de nível mais alto são mais simples para casos de uso comuns e são criados usando scrollableArea.

Diferença entre os modificadores scrollableArea e scrollable

A principal diferença entre scrollableArea e scrollable está na forma como eles interpretam os gestos de rolagem do usuário:

  • scrollable (delta bruto): o delta reflete diretamente o movimento físico da entrada do usuário (por exemplo, arrastar o ponteiro) na tela.
  • scrollableArea (delta orientado ao conteúdo): o delta é semanticamente invertido para representar a mudança selecionada na posição de rolagem e fazer com que o conteúdo pareça se mover com o gesto do usuário, que geralmente é o oposto do movimento do ponteiro.

Pense assim: scrollable informa como o ponteiro se moveu, enquanto scrollableArea traduz esse movimento em como o conteúdo deve se mover em uma visualização rolável típica. Essa inversão é o motivo pelo qual scrollableArea parece mais natural ao implementar um contêiner rolável padrão.

A tabela a seguir resume os sinais de delta para cenários comuns:

Interação inicial

Delta informado a dispatchRawDelta por scrollable

delta informado a dispatchRawDelta por scrollableArea*

O ponteiro se move para CIMA

Negativa

Positivo

O ponteiro se move para BAIXO

Positivo

Negativo

O ponteiro se move para a ESQUERDA

Negativo

Positivo (negativo para RTL)

O ponteiro se move para a DIREITA

Positivo

Negativo (positivo para RTL)

(*) Observação sobre o sinal delta de scrollableArea: o sinal do delta de scrollableArea não é apenas uma inversão simples. Ele considera de forma inteligente:

  1. Orientação: vertical ou horizontal.
  2. LayoutDirection: LTR ou RTL (especialmente importante para rolagem horizontal).
  3. Flag reverseScrolling: indica se a direção da rolagem está invertida.

Além de inverter o delta de rolagem, scrollableArea também corta o conteúdo nos limites do layout e processa a renderização de efeitos de rolagem excessiva. Por padrão, ele usa o efeito fornecido por LocalOverscrollFactory. Para personalizar ou desativar isso, use a sobrecarga scrollableArea que aceita um parâmetro OverscrollEffect.

Quando usar o modificador scrollableArea

Use o modificador scrollableArea quando precisar criar um componente de rolagem personalizado que não seja atendido adequadamente pelos modificadores horizontalScroll ou verticalScroll ou layouts Lazy. Isso geralmente envolve casos com:

  • Lógica de layout personalizada: quando a disposição dos itens muda dinamicamente com base na posição de rolagem.
  • Efeitos visuais exclusivos: aplicar transformações, dimensionamento ou outros efeitos às crianças enquanto elas rolam a tela.
  • Controle direto: necessidade de controle refinado sobre a mecânica de rolagem além do que verticalScroll ou layouts Lazy expõem.

Crie listas personalizadas semelhantes a rodas usando scrollableArea

O exemplo a seguir demonstra o uso de scrollableArea para criar uma lista vertical personalizada em que os itens diminuem à medida que se afastam do centro, criando um efeito visual semelhante a uma roda. Esse tipo de transformação dependente de rolagem é um caso de uso perfeito para scrollableArea.

Figura 2. Uma lista vertical personalizada usando scrollableArea.

@Composable
private fun ScrollableAreaSample() {
    // ...
    Layout(
        modifier =
            Modifier
                .size(150.dp)
                .scrollableArea(scrollState, Orientation.Vertical)
                .background(Color.LightGray),
        // ...
    ) { measurables, constraints ->
        // ...
        // Update the maximum scroll value to not scroll beyond limits and stop when scroll
        // reaches the end.
        scrollState.maxValue = (totalHeight - viewportHeight).coerceAtLeast(0)

        // Position the children within the layout.
        layout(constraints.maxWidth, viewportHeight) {
            // The current vertical scroll position, in pixels.
            val scrollY = scrollState.value
            val viewportCenterY = scrollY + viewportHeight / 2

            var placeableLayoutPositionY = 0
            placeables.forEach { placeable ->
                // This sample applies a scaling effect to items based on their distance
                // from the center, creating a wheel-like effect.
                // ...
                // Place the item horizontally centered with a layer transformation for
                // scaling to achieve wheel-like effect.
                placeable.placeRelativeWithLayer(
                    x = constraints.maxWidth / 2 - placeable.width / 2,
                    // Offset y by the scroll position to make placeable visible in the viewport.
                    y = placeableLayoutPositionY - scrollY,
                ) {
                    scaleX = scaleFactor
                    scaleY = scaleFactor
                }
                // Move to the next item's vertical position.
                placeableLayoutPositionY += placeable.height
            }
        }
    }
}
// ...

Modificador scrollable

O modificador scrollable é diferente dos modificadores de rolagem, porque scrollable detecta os gestos de rolagem e captura os deltas, mas não desloca o conteúdo automaticamente. Em vez disso, a delegação é feita ao usuário por ScrollableState , que é necessário para que esse modificador funcione corretamente.

Ao criar ScrollableState, é necessário fornecer uma função consumeScrollDelta que será invocada em cada etapa de rolagem (por entrada de gestos, rolagem suave ou deslizamento rápido) com o delta em pixels. Essa função precisa retornar a quantidade de distância de rolagem consumida. Isso é para garantir que o evento seja propagado corretamente nos casos em que há elementos aninhados com o modificador scrollable .

O snippet a seguir detecta os gestos e exibe um valor numérico para o deslocamento, mas não desloca os elementos:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableFloatStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Elemento da interface que detecta o pressionamento da tela e exibe o valor numérico referente ao local de pressionamento
Figura 3. Um elemento da interface que detecta o pressionamento da tela e exibe o valor numérico referente ao local de pressionamento.