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 composant fondamental pour créer des conteneurs personnalisés à faire défiler. 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 des gestes, le découpage du contenu et les effets de défilement au-delà des limites.

Bien que scrollableArea soit utilisé pour les implémentations personnalisées, vous devriez généralement privilégier les solutions prêtes à l'emploi telles que verticalScroll, horizontalScroll ou des composables tels que LazyColumn pour les listes déroulantes standards. Ces composants de niveau supérieur sont plus simples pour les cas d'utilisation courants et sont eux-mêmes créés à 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) sur l'écran.
  • scrollableArea (delta orienté contenu) : le delta est inversé sémantiquement pour représenter la modification sélectionnée de la position de défilement afin que le contenu semble se déplacer avec le geste de l'utilisateur, qui est généralement l'opposé du mouvement du pointeur.

En d'autres termes, scrollable vous indique comment le pointeur s'est déplacé, tandis que scrollableArea traduit ce mouvement du pointeur en mouvement du contenu dans une vue déroulante standard. Cette inversion explique pourquoi scrollableArea semble plus naturel lors de l'implémentation d'un conteneur déroulant 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

Positive (Negative pour RTL)

Le pointeur se déplace vers la DROITE

Positif

Négatif (positif pour les langues s'écrivant de droite à gauche)

(*) Remarque sur le signe delta scrollableArea : le signe du delta de scrollableArea n'est pas une simple inversion. Il prend intelligemment en compte :

  1. Orientation : verticale ou horizontale.
  2. LayoutDirection : de gauche à droite ou de droite à gauche (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 limite également le contenu aux limites de la mise en page et gère l'affichage des effets de défilement hors limites. 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 devez créer un composant de défilement personnalisé qui n'est pas correctement géré par les modificateurs horizontalScroll ou verticalScroll, ou par les mises en page différées. Cela implique souvent des cas avec :

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

Créez des listes personnalisées de type roue à l'aide de scrollableArea

L'exemple suivant montre comment utiliser scrollableArea pour créer une liste verticale personnalisée dans laquelle les éléments sont réduits à mesure qu'ils s'éloignent du centre, créant ainsi un effet visuel de "roue". Ce type de transformation dépendant 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 son contenu automatiquement. Cette tâche est 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.