Principes de base de la mise en page dans Compose

Jetpack Compose facilite grandement la conception et la création de l'interface utilisateur de votre application. Il transforme l'état en éléments d'interface utilisateur via trois étapes distinctes :

  1. La composition des éléments
  2. La mise en page des éléments
  3. Le dessin des éléments

Transformation de l'état en UI par Compose via la composition, la mise en page et le dessin

Ce document se concentre sur la mise en page. Il explique certains des composants fondamentaux proposés par Compose pour mettre en page les éléments d'interface utilisateur.

Objectifs des mises en page dans Compose

L'implémentation Jetpack Compose du système de mise en page répond à deux objectifs principaux :

Principes de base des fonctions modulables

Les fonctions composables sont les éléments de base de Compose. Une fonction modulable est une Unit émettant une fonction qui décrit une partie de votre interface utilisateur. Elle utilise des entrées et génère ce qui s'affiche à l'écran. Pour en savoir plus sur les composables, consultez la documentation sur le modèle mental de Compose.

Une fonction composable peut émettre plusieurs éléments d'interface utilisateur. Toutefois, si vous n'indiquez pas comment les organiser, Compose peut organiser les éléments d'une façon qui ne vous convient pas. Par exemple, ce code génère deux éléments de texte :

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Sans indications sur la façon dont vous souhaitez les organiser, Compose empile les éléments de texte les uns sur les autres, les rendant illisibles :

Deux éléments de texte tracés l'un au-dessus de l'autre, ce qui rend le texte illisible

Compose fournit une collection de mises en page prêtes à l'emploi pour vous aider à organiser les éléments d'interface utilisateur et à définir vos propres mises en page plus spécialisées.

Composants de mise en page standards

Dans de nombreux cas, vous pouvez simplement utiliser les éléments de mise en page standards de Compose.

Utilisez Column pour placer les éléments verticalement sur l'écran.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Deux éléments de texte disposés en colonnes pour que le texte soit lisible

De même, utilisez Row pour positionner les éléments horizontalement sur l'écran. Column et Row permettent tous deux de configurer l'alignement des éléments qu'ils contiennent.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Affichage d'une mise en page plus complexe, avec un petit graphique à côté d'une colonne d'éléments de texte

Utilisez Box pour superposer des éléments. Box permet également de configurer un alignement spécifique des éléments qu'il contient.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Affichage de deux éléments empilés

Souvent, ces composants de base suffisent. Vous pouvez écrire votre propre fonction modulable pour combiner ces mises en page dans une mise en page plus élaborée adaptée à votre application.

Comparaison de trois composables simples de mise en page : colonne, ligne et case

Pour définir la position des enfants dans un élément Row, définissez les arguments horizontalArrangement et verticalAlignment. Pour un élément Column, définissez les arguments verticalArrangement et horizontalAlignment :

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Les éléments sont alignés à droite

Le modèle de mise en page

Dans le modèle de mise en page, l'arborescence de l'UI est mise en page en une seule fois. Chaque nœud doit d'abord se mesurer, puis mesurer les éléments enfants de manière récursive, en leur transmettant les contraintes de taille. Ensuite, les nœuds feuilles sont redimensionnés et positionnés, tandis que les tailles et les instructions de positionnement résolues sont renvoyées à l'arborescence.

En bref, les parents se mesurent avant leurs enfants, mais ils sont dimensionnés et positionnés après leurs enfants.

Prenons l'exemple de la fonction SearchResult suivante.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Cette fonction génère l'arborescence d'interface utilisateur ci-dessous.

SearchResult
  Row
    Image
    Column
      Text
      Text

Dans l'exemple SearchResult, l'arborescence de l'interface utilisateur suit cet ordre :

  1. Le nœud racine Row doit se mesurer.
  2. Le nœud racine Row demande à son premier enfant, Image, de se mesurer.
  3. Image est un nœud feuille (nœud qui n'a pas d'enfants). Il renvoie donc une taille et renvoie des instructions de positionnement.
  4. Le nœud racine Row demande à son deuxième enfant, Column, de se mesurer.
  5. Le nœud Column demande à son premier enfant Text de se mesurer.
  6. Le premier nœud Text est un nœud feuille. Il renvoie donc une taille et renvoie des instructions de positionnement.
  7. Le nœud Column demande à son deuxième enfant Text de se mesurer.
  8. Le deuxième nœud Text est un nœud feuille. Il renvoie donc une taille et renvoie des instructions de positionnement.
  9. Maintenant que le nœud Column a mesuré, dimensionné et positionné ses enfants, il peut déterminer sa propre taille et son propre emplacement.
  10. Maintenant que le nœud racine Row a mesuré, dimensionné et positionné ses enfants, il peut déterminer sa propre taille et son propre emplacement.

Ordre de la mesure, du dimensionnement et du positionnement dans l'arborescence de l'UI de résultats de recherche

Performances

Compose atteint de hautes performances en ne mesurant les enfants qu'une seule fois. Il peut ainsi gérer efficacement les arborescences profondes. Si un élément mesurait deux fois son enfant et que celui-ci mesurait deux fois chacun de ses enfants, et ainsi de suite, une seule tentative de mise en page de l'ensemble de l'UI entraînerait une charge de travail considérable. Il serait donc difficile de conserver le niveau de performances de l'application.

Si, pour une raison quelconque, votre mise en page nécessite plusieurs mesures, Compose propose un système spécial de mesures intrinsèques. Pour en savoir plus sur cette fonctionnalité, consultez la section Mesures intrinsèques dans les mises en page Compose.

Étant donné que les mesures et les positionnements constituent des sous-phases distinctes de la mise en page, toutes les modifications qui n'affectent que le positionnement des éléments, et non les mesures, peuvent être exécutées séparément.

Utiliser des modificateurs dans les mises en page

Comme indiqué dans la section Modificateurs Compose, vous pouvez utiliser des modificateurs pour décorer ou enrichir vos composables. Ils sont essentiels pour personnaliser votre mise en page. Par exemple, voici plusieurs modificateurs associés les uns aux autres pour personnaliser l'élément ArtistCard :

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Mise en page encore plus complexe, avec des modificateurs permettant de changer la disposition des graphiques et de définir les zones qui réagissent aux entrées utilisateur

Dans le code ci-dessus, vous remarquerez que plusieurs fonctions sont utilisées.

  • clickable fait réagir un composable suite à une entrée utilisateur et affiche une onde.
  • padding ajoute de l'espace autour d'un élément.
  • fillMaxWidth fait en sorte que le composable remplisse la largeur maximale qui lui est attribuée par son parent.
  • size() spécifie la largeur et la hauteur préférées d'un élément.

Mises en page à défilement

Pour en savoir plus sur les mises en page á défilement, consultez la documentation sur les gestes Compose.

Pour en savoir plus sur les listes standards et les listes différées, consultez la documentation spécifique aux listes Compose.

Mises en page responsives

Une mise en page doit être conçue en tenant compte des différentes orientations d'écran et des différents facteurs de forme possibles. Compose propose des mécanismes prêts à l'emploi permettant d'adapter les mises en page modulables à différentes configurations d'écran.

Contraintes

Pour déterminer les contraintes du parent et concevoir la mise en page en conséquence, vous pouvez utiliser un élément BoxWithConstraints. Les contraintes de mesure se trouvent dans le champ d'application du lambda de contenu. Vous pouvez utiliser ces contraintes de mesure afin de créer différentes mises en page pour différentes configurations d'écran :

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Mises en page basées sur les emplacements

Compose fournit une grande variété de composables basés sur Material Design avec la dépendance androidx.compose.material:material (ajoutée lors de la création d'un projet Compose dans Android Studio) pour faciliter la création de l'interface utilisateur. Les éléments tels que Drawer, FloatingActionButton et TopAppBar sont tous fournis.

Les composants Material font beaucoup appel aux API d'emplacement, un modèle proposé par Compose pour ajouter une couche de personnalisation aux composables. Cette approche rend les composants plus flexibles, car ils acceptent un élément enfant qui peut se configurer lui-même au lieu d'avoir à exposer tous ses paramètres de configuration. Ces emplacements laissent dans l'UI un espace vide que le développeur peut remplir comme bon lui semble. Par exemple, voici les emplacements que vous pouvez personnaliser dans un objet TopAppBar :

Schéma représentant les emplacements disponibles dans une barre d'application de composants Material

Les composables utilisent généralement un lambda content (content: @Composable () -> Unit). Les API d'emplacement présentent plusieurs paramètres content pour des utilisations spécifiques. Par exemple, TopAppBar vous permet de fournir le contenu pour title, navigationIcon et actions.

Par exemple, Scaffold vous permet d'implémenter une UI avec la structure de mise en page Material Design de base. Scaffold fournit des emplacements pour les composants Material de niveau supérieur les plus courants, tels que TopAppBar, BottomAppBar, FloatingActionButton et Drawer. Avec Scaffold, vous pouvez facilement vous assurer que ces composants sont correctement positionnés et interagissent comme prévu.

Application JetNews, qui utilise Scaffold pour positionner plusieurs éléments

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}