Listes avec Compose pour Wear OS


Grâce aux listes, les utilisateurs peuvent faire leur choix parmi un ensemble d'éléments sur des appareils Wear OS.

De nombreux appareils Wear OS utilisent des écrans ronds, ce qui rend plus difficile la visibilité des éléments de liste qui apparaissent en haut et en bas de l'écran. C'est la raison pour laquelle Compose pour Wear OS inclut une version de la classe LazyColumn appelée TransformingLazyColumn, qui prend en charge les animations de mise à l'échelle et de morphing. Lorsque les éléments se déplacent vers les bords, ils deviennent plus petits et s'estompent.

Pour appliquer les effets de mise à l'échelle et de défilement recommandés :

  1. Utilisez Modifier.transformedHeight pour permettre à Compose de calculer le changement de hauteur lorsque l'élément défile sur l'écran.
  2. Utilisez transformation = SurfaceTransformation(transformationSpec) pour appliquer les effets visuels, y compris la réduction de la taille du contenu de l'élément.
  3. Utilisez un TransformationSpec personnalisé pour les composants qui n'acceptent pas transformation comme paramètre, tels que Text.

L'animation suivante montre comment un élément de liste change de taille et de forme lorsqu'il s'approche du haut et du bas de l'écran :

L'extrait de code suivant montre comment créer une liste à l'aide de TransformingLazyColumn mise en page pour créer du contenu qui s'affiche correctement sur différentes tailles d'écran Wear OS.

L'extrait montre également l'utilisation du modificateur minimumVerticalContentPadding, que vous devez définir sur les éléments de la liste pour appliquer le remplissage approprié en haut et en bas de la liste.

Pour afficher l'indicateur de défilement, partagez le columnState entre le ScreenScaffold et le TransformingLazyColumn :

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

Ajouter un effet d'ancrage et de glissement

L'ancrage garantit que lorsqu'un utilisateur termine un geste de défilement ou de glissement, la liste s'arrête avec un élément positionné précisément à un point spécifique, généralement au centre de l'écran. Sur les écrans ronds, où les éléments changent de taille et de forme à mesure qu'ils s'éloignent du centre, l'ancrage est particulièrement utile pour s'assurer que l'élément le plus pertinent reste entièrement visible et lisible dans la zone de visualisation optimale.

Pour ajouter un comportement d'ancrage et de glissement, définissez le paramètre flingBehavior sur TransformingLazyColumnDefaults.snapFlingBehavior(columnState). Définissez le rotaryScrollableBehavior pour qu'il corresponde, en utilisant RotaryScrollableDefaults.snapBehavior(columnState) pour une expérience cohérente lorsque vous utilisez la couronne ou la lunette physique.

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

Mise en page inversée

Par défaut, une liste déroulante s'ancre à son bord supérieur. Si un utilisateur a fait défiler une liste standard jusqu'en bas et qu'un nouvel élément est ajouté à la fin, la liste conserve la vue de l'utilisateur sur l'élément actuel. Par exemple, si l'utilisateur consulte l'élément 10 en bas de l'écran et que l'élément 11 est ajouté, la vue reste axée sur l'élément 10, et l'élément 11 apparaît hors écran sous la vue actuelle.

Pour les cas d'utilisation tels que les applications de messagerie ou les journaux en direct, ce comportement n'est généralement pas souhaité. Lorsque de nouveaux éléments arrivent, les utilisateurs souhaitent généralement voir immédiatement le contenu le plus récent s'ils se trouvent déjà en bas de la liste. Si de nombreux éléments arrivent en même temps, la liste doit passer à l'élément le plus récent en bas (ce qui signifie que certains éléments intermédiaires peuvent ne pas s'afficher du tout, sauf si l'utilisateur fait défiler la liste vers le haut).

Pour prendre en charge ces cas d'utilisation, TransformingLazyColumn vous permet d'inverser la mise en page en définissant reverseLayout = true. L'ancre de la liste passe alors du bord supérieur au bord inférieur.

Pour plus de commodité, la définition de reverseLayout = true inverse également l'ordre visuel des éléments et le sens des gestes de défilement :

  • Les éléments sont composés de bas en haut, ce qui signifie que l'index 0 apparaît en bas de l'écran.
  • Le défilement vers le haut révèle les éléments avec des index plus élevés.

Pour ajouter un comportement d'ancrage et de glissement avec une mise en page inversée, vous pouvez combiner flingBehavior et rotaryScrollableBehavior, comme illustré dans l'extrait suivant :

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

Les images suivantes montrent la différence entre une liste normale et une liste inversée :

TransformingLazyColumn avec une mise en page normale, affichant l'élément 1 en haut et les éléments dans l'ordre croissant.
Figure 1. Mise en page de liste standard où le contenu s'affiche de haut en bas.
A TransformingLazyColumn with reverse layout, showing Item 1 at the bottom and items in descending order towards the top.
Figure 2. Mise en page de liste inversée où le contenu s'affiche de bas en haut.

Boutons de bord sur les listes

Pour Material 3, vous pouvez ajouter un EdgeButton, qui est un bouton de bord en bas des listes. Toutefois, veillez à ne pas l'ajouter en tant qu'élément dans le TransformingLazyColumn, mais plutôt à utiliser l'emplacement edgeButton dans le ScreenScaffold.

L'utilisation de l'emplacement edgeButton garantit que le bouton est correctement positionné en bas de l'écran et qu'il se comporte de manière appropriée lorsque la liste défile.

L'extrait de code suivant montre comment implémenter EdgeButton :

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = columnState,
    edgeButton = {
        EdgeButton(
            onClick = { /* TODO */ },
            modifier = Modifier.scrollable(
                columnState,
                orientation = Orientation.Vertical,
                reverseDirection = true,
                // Apply overscroll to the EdgeButton for proper scrolling behavior.
                overscrollEffect = rememberOverscrollEffect(),
            )
        ) {
            Text("More")
        }
    }
) { contentPadding ->
    TransformingLazyColumn(
        contentPadding = contentPadding,
        state = columnState,
    ) {
        // ...
        // ...
    }
}

SwipeToReveal dans les listes

Le composant SwipeToReveal vous permet d'accéder aux actions d'un élément de liste, tel qu'un Card ou un Chip, en balayant l'écran. Le balayage révèle généralement un ou deux boutons d'action (tels que "Supprimer" ou "Plus") sur le côté.

Lorsque vous utilisez SwipeToReveal dans un TransformingLazyColumn, suivez ces consignes :

  • Réinitialiser lors du défilement : lorsque l'utilisateur fait défiler la liste, réinitialisez tous les éléments ouverts par balayage à leur état couvert.
  • Hauteurs cohérentes : définissez les hauteurs des boutons d'action pour qu'elles correspondent à l'élément balayé interne (qu'il s'agisse d'un Button ou d'une Card) afin de garantir une apparence cohérente.
  • Transformer le conteneur : appliquez le modificateur transformedHeight et transformationSpec au composant SwipeToReveal lui-même.
  • Ne pas effectuer de double transformation : n'appliquez pas les modificateurs transformedHeight ou transformation à l'élément balayé interne (la carte ou le bouton à l'intérieur du conteneur SwipeToReveal).