Scorri

Modificatori di scorrimento

I modificatori verticalScroll e horizontalScroll offrono il modo più semplice per consentire all'utente di scorrere un elemento quando i limiti dei suoi contenuti sono maggiori dei vincoli di dimensione massima. Con i modificatori verticalScroll e horizontalScroll non è necessario tradurre o compensare i contenuti.

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

Un semplice elenco verticale che risponde ai gesti di scorrimento
Figura 1. Un semplice elenco verticale che risponde ai gesti di scorrimento.

ScrollState consente di modificare la posizione di scorrimento o ottenere il suo stato attuale. Per crearlo con i parametri predefiniti, utilizza 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))
        }
    }
}

Modificatore dell'area scorrevole

Il modificatore scrollableArea è un elemento costitutivo fondamentale per la creazione di contenitori scorrevoli personalizzati. Fornisce un'astrazione di livello superiore rispetto al modificatore scrollable, gestendo requisiti comuni come l'interpretazione del delta dei gesti, il ritaglio dei contenuti e gli effetti di overscroll.

Sebbene scrollableArea venga utilizzato per implementazioni personalizzate, in genere dovresti preferire soluzioni pronte all'uso come verticalScroll, horizontalScroll o componibili come LazyColumn per elenchi di scorrimento standard. Questi componenti di livello superiore sono più semplici per i casi d'uso comuni e sono costruiti utilizzando scrollableArea.

Differenza tra i modificatori scrollableArea e scrollable

La differenza principale tra scrollableArea e scrollable risiede nel modo in cui interpretano i gesti di scorrimento dell'utente:

  • scrollable (delta grezzo): il delta riflette direttamente il movimento fisico dell'input dell'utente (ad es. trascinamento del puntatore) sullo schermo.
  • scrollableArea (delta orientato ai contenuti): il delta è invertito semanticamente per rappresentare la variazione selezionata nella posizione di scorrimento in modo che i contenuti sembrino muoversi con il gesto dell'utente, che di solito è l'opposto del movimento del puntatore.

Immagina che scrollable ti dica come si è spostato il puntatore, mentre scrollableArea traduce il movimento del puntatore in come i contenuti devono muoversi all'interno di una tipica visualizzazione scorrevole. Questa inversione è il motivo per cui scrollableArea risulta più naturale quando si implementa un contenitore scorrevole standard.

La tabella seguente riepiloga i segni di variazione per gli scenari comuni:

Attivazione dall'utente

delta segnalato a dispatchRawDelta da scrollable

delta segnalato a dispatchRawDelta entro il giorno scrollableArea*

Il puntatore si sposta VERSO L'ALTO

Negativo

Positivo

Il puntatore si sposta IN BASSO

Positivo

Negativo

Il puntatore si sposta a SINISTRA

Negativo

Positivo (Negativo per RTL)

Il puntatore si sposta a DESTRA

Positivo

Negativo (positivo per RTL)

(*) Nota sul segno delta scrollableArea: il segno del delta da scrollableArea non è una semplice inversione. Prende in considerazione in modo intelligente:

  1. Orientamento: verticale o orizzontale.
  2. LayoutDirection: da sinistra a destra o da destra a sinistra (particolarmente importante per lo scorrimento orizzontale).
  3. Flag reverseScrolling: indica se la direzione di scorrimento è invertita.

Oltre a invertire il delta di scorrimento, scrollableArea ritaglia anche i contenuti in base ai limiti del layout e gestisce il rendering degli effetti di overscroll. Per impostazione predefinita, utilizza l'effetto fornito da LocalOverscrollFactory. Puoi personalizzare o disattivare questa funzionalità utilizzando l'overload scrollableArea che accetta un parametro OverscrollEffect.

Quando utilizzare il modificatore scrollableArea

Devi utilizzare il modificatore scrollableArea quando devi creare un componente di scorrimento personalizzato che non viene gestito in modo adeguato dai modificatori horizontalScroll o verticalScroll o dai layout pigri. Spesso si tratta di casi con:

  • Logica di layout personalizzata: quando la disposizione degli elementi cambia dinamicamente in base alla posizione di scorrimento.
  • Effetti visivi unici: applicazione di trasformazioni, ridimensionamenti o altri effetti ai bambini mentre scorrono.
  • Controllo diretto: necessità di un controllo granulare sui meccanismi di scorrimento oltre a quanto esposto da verticalScroll o dai layout pigri.

Creare elenchi personalizzati a forma di ruota utilizzando scrollableArea

Il seguente esempio mostra l'utilizzo di scrollableArea per creare un elenco verticale personalizzato in cui gli elementi vengono ridimensionati man mano che si allontanano dal centro, creando un effetto visivo simile a una ruota. Questo tipo di trasformazione dipendente dallo scorrimento è un caso d'uso perfetto per scrollableArea.

Figura 2. Un elenco verticale personalizzato che utilizza 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
            }
        }
    }
}
// ...

Modificatore scorrevole

Il modificatore scrollable è diverso dai modificatori di scorrimento in quanto scrollable rileva i gesti di scorrimento e acquisisce i delta, ma non compensa automaticamente i contenuti. Questa operazione viene invece delegata all'utente tramite ScrollableState , che è necessaria per il corretto funzionamento di questo modificatore.

Quando crei ScrollableState, devi fornire una funzione consumeScrollDelta che verrà richiamata a ogni passaggio di scorrimento (tramite input tramite gesto, scorrimento fluido o scorrimento rapido) con il delta in pixel. Questa funzione deve restituire la quantità di distanza di scorrimento consumata, per garantire che l'evento venga propagato correttamente nei casi in cui sono presenti elementi nidificati con il modificatore scrollable.

Il seguente snippet rileva i gesti e mostra un valore numerico per un offset, ma non sposta alcun elemento:

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

Un elemento dell'interfaccia utente che rileva la pressione del dito e mostra il valore numerico della posizione del dito
Figura 3. Un elemento dell'interfaccia utente che rileva la pressione del dito e mostra il valore numerico della posizione del dito.