Faire défiler

Modificateurs Scroll

Les modificateurs verticalScroll et horizontalScroll sont la solution la plus simple pour autoriser l'utilisateur à faire défiler un élément lorsque les limites de son contenu dépassent les contraintes de taille maximales. Avec les modificateurs verticalScroll et horizontalScroll, vous n'avez pas besoin de décaler le contenu ni d'effectuer une translation.

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

Liste verticale simple réagissant à des gestes de défilement
Figure 1. Liste verticale simple réagissant à des gestes de défilement.

Le ScrollState vous permet de modifier la position de défilement ou d'obtenir son état actuel. Pour le créer avec des paramètres par défaut, utilisez 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))
        }
    }
}

Modificateur de zone de défilement

Le modificateur scrollableArea est un élément fondamental pour créer des conteneurs à défilement personnalisés. Il fournit une abstraction de niveau supérieur par rapport au modificateur scrollable, en gérant les exigences courantes telles que l'interprétation du delta de geste, le clipping de contenu et les effets de surdéfilement.

Bien que scrollableArea soit utilisé pour les implémentations personnalisées, vous devriez généralement préférer les solutions prêtes à l'emploi telles que verticalScroll, horizontalScroll ou les composables tels que LazyColumn pour les listes de défilement standards. Ces composants de niveau supérieur sont plus simples pour les cas d'utilisation courants et sont eux-mêmes construits à l'aide de scrollableArea.

Différence entre les modificateurs scrollableArea et scrollable

La principale différence entre scrollableArea et scrollable réside dans la façon dont ils interprètent les gestes de défilement de l'utilisateur :

  • scrollable (delta brut) : le delta reflète directement le mouvement physique de l'entrée de l'utilisateur (par exemple, le déplacement du pointeur) à l'écran.
  • scrollableArea (delta orienté contenu) : delta est inversé sémantiquement pour représenter le changement sélectionné dans la position de défilement afin que le contenu semble se déplacer avec le geste de l'utilisateur, ce qui est généralement l'opposé du mouvement du pointeur.

Pour faire simple, scrollable vous indique comment le pointeur s'est déplacé, tandis que scrollableArea traduit ce déplacement du pointeur en déplacement du contenu dans une vue défilable classique. Cette inversion explique pourquoi scrollableArea semble plus naturel lors de l'implémentation d'un conteneur à défilement standard.

Le tableau suivant récapitule les signes delta pour les scénarios courants :

Geste de l'utilisateur

Delta signalé à dispatchRawDelta par scrollable

Delta signalé à dispatchRawDelta par scrollableArea*

Le pointeur se déplace VERS LE HAUT

Négatif

Positif

Le pointeur se déplace vers le BAS.

Positif

Négatif

Le pointeur se déplace vers la gauche.

Négatif

Positif (négatif pour RTL)

Le pointeur se déplace vers la DROITE

Positif

Négatif (positif pour RTL)

(*) Remarque sur le signe delta de scrollableArea : Le signe du delta de scrollableArea n'est pas une simple inversion. Il tient compte de manière intelligente des éléments suivants :

  1. Orientation : verticale ou horizontale.
  2. LayoutDirection : LTR ou RTL (particulièrement important pour le défilement horizontal).
  3. Indicateur reverseScrolling : indique si le sens de défilement est inversé.

En plus d'inverser le delta de défilement, scrollableArea découpe également le contenu aux limites de la mise en page et gère le rendu des effets de dépassement de défilement. Par défaut, il utilise l'effet fourni par LocalOverscrollFactory. Vous pouvez personnaliser ou désactiver cette option en utilisant la surcharge scrollableArea qui accepte un paramètre OverscrollEffect.

Quand utiliser le modificateur scrollableArea

Vous devez utiliser le modificateur scrollableArea lorsque vous avez besoin de créer un composant de défilement personnalisé qui n'est pas correctement géré par les modificateurs horizontalScroll ou verticalScroll, ni par les mises en page Lazy. Cela concerne souvent les cas suivants :

  • Logique de mise en page personnalisée : lorsque l'agencement des éléments change de manière dynamique en fonction de la position de défilement.
  • Effets visuels uniques : application de transformations, de mises à l'échelle ou d'autres effets aux enfants lors du défilement.
  • Contrôle direct : vous avez besoin d'un contrôle précis sur les mécanismes de défilement au-delà de ce que verticalScroll ou les mises en page Lazy exposent.

Créer des listes personnalisées en forme de roue à l'aide de scrollableArea

L'exemple suivant montre comment utiliser scrollableArea pour créer une liste verticale personnalisée où les éléments diminuent à mesure qu'ils s'éloignent du centre, créant ainsi un effet visuel de type "roue". Ce type de transformation dépendante du défilement est un cas d'utilisation idéal pour scrollableArea.

Figure 2. Liste verticale personnalisée à l'aide de 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
            }
        }
    }
}
// ...

Modificateur Scrollable

Le modificateur scrollable diffère des modificateurs Scroll dans le sens où scrollable détecte les gestes de défilement et capture les deltas, mais ne décale pas automatiquement son contenu. Elle est plutôt déléguée à l'utilisateur via ScrollableState, qui est nécessaire pour que ce modificateur fonctionne correctement.

Lorsque vous construisez ScrollableState, vous devez fournir une fonction consumeScrollDelta qui sera appelée à chaque étape de défilement (par saisie gestuelle, défilement fluide ou glissement d'un geste vif) avec le delta en pixels. Cette fonction doit renvoyer la distance de défilement consommée, pour garantir que l'événement se propage correctement dans les cas où des éléments imbriqués disposent du modificateur scrollable.

L'extrait de code suivant détecte les gestes et affiche une valeur numérique correspondant à un décalage, mais ne décale aucun élément :

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

Élément d'interface utilisateur qui détecte la pression d'un doigt et affiche la valeur numérique de sa position
Figure 3. Élément d'interface utilisateur qui détecte la pression d'un doigt et affiche la valeur numérique de sa position.