Mises en page de base dans Compose

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

1. Introduction

En tant que kit d'UI, Compose facilite l'implémentation des conceptions de votre application. Vous décrivez l'apparence que vous souhaitez pour votre UI, et Compose se charge de la dessiner à l'écran. Dans cet atelier de programmation, vous apprendrez à écrire des UI Compose. Nous partons du principe que vous comprenez les concepts enseignés dans l'atelier de programmation sur les principes de base. Dès lors, assurez-vous de commencer par cet atelier. Dans cet atelier sur les principes de base, vous avez appris à implémenter des mises en page simples à l'aide de Surfaces, Rows et Columns. Vous avez également enrichi ces mises en page avec des modificateurs tels que padding, fillMaxWidth et size.

Dans cet atelier de programmation, vous allez implémenter une mise en page plus réaliste et plus complexe, et découvrir différents modificateurs et composables prêts à l'emploi. À la fin de cet atelier de programmation, vous devriez être en mesure de transformer la conception d'une application de base en code fonctionnel.

Cet atelier de programmation n'ajoute aucun comportement réel à l'application. Si vous souhaitez en savoir plus sur les états et les interactions, suivez plutôt l'atelier de programmation L'état dans Compose.

Pour obtenir de l'aide tout au long de cet atelier de programmation, reportez-vous au code suivant :

Points abordés

Cet atelier de programmation traite des points suivants :

  • Comment les modificateurs vous aident à enrichir vos composables.
  • Comment les composants de mise en page standards, tels que Column et LazyRow, positionnent les composables enfants.
  • Comment les alignements et les dispositions modifient la position des composables enfants dans leur élément parent
  • Comment les composables Material, tels que Scaffold et Bottom Navigation, vous aident à créer des mises en page complètes
  • Comment créer des composables flexibles à l'aide des API d'emplacement

Ce dont vous avez besoin

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez implémenter une conception d'application réaliste sur la base de maquettes fournies par un concepteur. MySoothe est une application de bien-être qui liste différents moyens d'améliorer votre santé physique et mentale. Il contient une section qui présente vos collections préférées et une autre avec des exercices physiques. Voici comment se présente l'application :

24ff9efa75f22198.png

2. Configuration

Au cours de cette étape, vous allez télécharger un code contenant des thèmes et une configuration de base.

Obtenir le code

Le code de cet atelier de programmation est disponible dans le dépôt GitHub android-compose-codelabs. Pour le cloner, exécutez la commande suivante :

$ git clone https://github.com/googlecodelabs/android-compose-codelabs

Vous pouvez également télécharger deux fichiers ZIP :

Consulter le code

Le code téléchargé contient du code pour tous les ateliers de programmation Compose disponibles. Pour cet atelier, ouvrez le projet BasicLayoutsCodelab dans Android Studio.

Nous vous recommandons de commencer par le code de la branche main, puis de suivre l'atelier étape par étape, à votre propre rythme.

3. Commencer par un plan

Penchons-nous sur la conception :

c31e78e48cc1f336.png

Lorsque vous êtes invité à implémenter une conception, l'examen de sa structure constitue un bon point de départ. Ne commencez pas immédiatement le codage, mais analysez plutôt la conception proprement dite. Comment scinder cette UI en plusieurs parties réutilisables ?

Voyons cela avec notre conception. Au niveau d'abstraction le plus élevé, nous pouvons décomposer cette conception en deux parties :

  • Contenu de l'écran.
  • Navigation en bas de l'écran.

9a0f4be94a5a206c.png

En y regardant d'un peu plus près, on constate que le contenu de l'écran se divise en trois sous-parties :

  • Barre de recherche.
  • Une section intitulée "Align your body" (Aligner votre corps).
  • Une section intitulée "Favorite collections" (Collections préférées).

d9bf2ca5a0939959.png

Chaque section comprend également des composants de niveau inférieur qui sont réutilisés :

  • Élément "Align your body" affiché sur une ligne à défilement horizontal.

29bed1f813622dc.png

  • Fiche "Favorite collections" affichée dans une grille à défilement horizontal.

cf1fe8b2d682bfca.png

Maintenant que vous avez analysé la conception, vous pouvez commencer à implémenter des composables pour chaque élément identifié de l'interface utilisateur. Commencez par les composables du niveau le plus bas, puis continuez à les combiner en éléments plus complexes. À la fin de l'atelier de programmation, votre nouvelle application ressemblera à la conception fournie.

4. Barre de recherche : modificateurs

Le premier élément à transformer en composable est la barre de recherche. Revenons un instant à la conception :

6b7c2f913d189b9a.png

En se basant uniquement sur cette capture d'écran, il serait assez difficile d'implémenter la conception de façon optimale. En règle générale, un concepteur transmet plus d'informations sur la conception. Il peut vous donner accès à son outil de conception ou partager ce que l'on appelle des conceptions de révision (avec des lignes rouges). Dans le cas présent, notre concepteur a fourni des conceptions de révision que vous pouvez utiliser pour lire n'importe quelle valeur de dimensionnement. La conception est illustrée avec une superposition de grille de 8 dp. Vous pouvez ainsi voir facilement l'espace entre et autour des éléments. De plus, certains espacements sont explicitement ajoutés pour clarifier certaines tailles.

6c6854661a89e995.png

Comme vous pouvez le voir, la barre de recherche doit avoir une hauteur de 56 pixels indépendants de la densité. Elle doit également occuper toute la largeur de son élément parent.

Pour implémenter la barre de recherche, utilisez un composant Material appelé champ de texte. La bibliothèque Material de Compose contient un composable appelé TextField, qui est l'implémentation de ce composant Material.

Commencez par une implémentation TextField de base. Dans votre code base, ouvrez MainActivity.kt et recherchez le composable SearchBar.

Dans le composable appelé SearchBar, écrivez l'implémentation TextField de base :

import androidx.compose.material.TextField

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       modifier = modifier
   )
}

Quelques points à noter :

  • Vous avez codé en dur la valeur du champ de texte et le rappel onValueChange n'a aucun effet. Puisqu'il s'agit d'un atelier de programmation axé sur la mise en page, vous ignorez tout ce qui concerne l'état.
  • La fonction modulable SearchBar accepte un paramètre modifier, qu'elle transmet à TextField. Il s'agit d'une bonne pratique conformément aux consignes de Compose. Cela permet à l'appelant de la méthode de modifier l'apparence du composable, ce qui le rend plus flexible et plus facile à réutiliser. Vous allez appliquer cette bonne pratique à tous les composables de cet atelier de programmation.

Observons l'aperçu de ce composable. Pour rappel, vous pouvez utiliser la fonctionnalité d'aperçu d'Android Studio pour itérer rapidement vos différents composables. MainActivity.kt contient des aperçus de tous les composables que vous allez créer dans cet atelier de programmation. Dans le cas présent, la méthode SearchBarPreview effectue un rendu de notre composable SearchBar, avec un arrière-plan et une marge intérieure qui lui donnent un peu plus de contexte. Avec l'implémentation que vous venez d'ajouter, vous devriez obtenir le résultat suivant :

c2e1eec30f36bc72.png

Il manque certaines choses. Commençons par corriger la taille du composable à l'aide de modificateurs.

Lorsque vous écrivez des composables, vous utilisez des modificateurs pour les opérations suivantes :

  • Modifier la taille, la mise en page, le comportement et l'apparence du composable.
  • Ajouter des informations, comme des libellés d'accessibilité.
  • Traiter les entrées utilisateur.
  • Ajouter des interactions de haut niveau (par exemple, faire en sorte que l'utilisateur puisse cliquer sur un élément, le faire défiler, le déplacer ou zoomer dessus).

Chaque composable que vous appelez possède un paramètre modifier que vous pouvez définir afin d'adapter son apparence et son comportement. Lorsque vous définissez le modificateur, vous pouvez enchaîner plusieurs méthodes de modification pour créer une adaptation plus complexe.

Dans ce cas, la barre de recherche doit avoir une hauteur d'au moins 56 dp et remplir la largeur de l'élément parent. Pour trouver les modificateurs appropriés, vous pouvez parcourir la liste des modificateurs et consulter la section Taille. Pour la hauteur, vous pouvez utiliser le modificateur heightIn. Vous avez ainsi la garantie que le composable a une hauteur minimale spécifique. Il peut toutefois être plus grand lorsque, par exemple, l'utilisateur augmente la taille de sa police système. Pour la largeur, vous pouvez utiliser le modificateur fillMaxWidth. Ce modificateur garantit que la barre de recherche utilise tout l'espace horizontal de l'élément parent.

Mettez à jour le modificateur pour qu'il corresponde au code ci-dessous :

import androidx.compose.material.TextField

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       modifier = modifier
           .fillMaxWidth()
           .heightIn(min = 56.dp)
   )
}

Dans le cas présent, l'ordre des modificateurs n'a pas d'importance, dans la mesure où l'un a une influence sur la largeur et l'autre, sur la hauteur.

Vous devez également définir certains paramètres de TextField. Essayez de faire en sorte que le composable ressemble à la conception en définissant les valeurs des paramètres. Voici à nouveau la conception à titre de référence :

6b7c2f913d189b9a.png

Pour mettre à jour votre implémentation, procédez comme suit :

  • Ajoutez l'icône de recherche. TextField contient un paramètre leadingIcon qui accepte un autre composable. À l'intérieur, vous pouvez définir un élément Icon qui, dans notre cas, devrait être Search. Veillez à utiliser l'importation Icon Compose appropriée.
  • Définissez la couleur d'arrière-plan du champ de texte sur la couleur surface de MaterialTheme. Vous pouvez utiliser TextFieldDefaults.textFieldColors pour remplacer des couleurs spécifiques.
  • Ajoutez un texte d'espace réservé "Search" (vous pouvez le trouver en tant que ressource de chaîne R.string.placeholder_search).

Une fois que vous avez terminé, votre composable doit se présenter comme suit :

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.ui.res.stringResource
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search

@Composable
fun SearchBar(
   modifier: Modifier = Modifier
) {
   TextField(
       value = "",
       onValueChange = {},
       leadingIcon = {
           Icon(
               imageVector = Icons.Default.Search,
               contentDescription = null
           )
       },
       colors = TextFieldDefaults.textFieldColors(
           backgroundColor = MaterialTheme.colors.surface
       ),
       placeholder = {
           Text(stringResource(R.string.placeholder_search))
       },
       modifier = modifier
           .fillMaxWidth()
           .heightIn(min = 56.dp)
   )
}

Remarque :

  • Vous avez ajouté un élément leadingIcon affichant l'icône de recherche. La description du contenu de cette icône n'est pas nécessaire, car l'espace réservé du champ de texte permet déjà d'en connaître la signification. N'oubliez pas qu'une description de contenu est généralement utilisée à des fins d'accessibilité et donne à l'utilisateur de votre application une représentation textuelle d'une image ou d'une icône.
  • Pour adapter la couleur d'arrière-plan du champ de texte, vous devez définir la propriété colors. Au lieu d'un paramètre distinct pour chaque couleur, le composable contient un paramètre combiné. Vous transmettez ici une copie de la classe de données TextFieldDefaults, dans laquelle vous ne mettez à jour que les couleurs différentes. Dans le cas présent, il ne s'agit que de la couleur d'arrière-plan.
  • Vous devez définir une hauteur minimale, et non une hauteur fixe. Il s'agit de la méthode recommandée pour que la taille du champ de texte puisse encore augmenter lorsque, par exemple, l'utilisateur augmente la taille de sa police dans les paramètres système.

Au cours de cette étape, vous avez vu comment utiliser des paramètres et des modificateurs composables pour modifier l'apparence d'un composable. Cela s'applique aussi bien aux composables fournis par les bibliothèques Compose et Material qu'à ceux que vous écrivez vous-même. Vous devez toujours penser à fournir des paramètres pour personnaliser le composable que vous écrivez. Vous devez également ajouter une propriété modifier pour que l'apparence du composable puisse être adaptée depuis l'extérieur.

5. Align your body : alignement

Le prochain composable que vous allez implémenter est l'élément "Align your body". Observons son apparence, y compris la conception de révision (lignes rouges) affichée à côté :

29bed1f813622dc.png 9d11e16a8817686f.png

À présent, la conception de révision contient également des espacements orientés vers la ligne de base. Voici les informations que nous pouvons en tirer :

  • La hauteur de l'image doit être de 88 dp.
  • L'espacement entre la ligne de base du texte et l'image doit être de 24 dp.
  • L'espacement entre la ligne de base et le bas de l'élément doit être de 8 dp.
  • Le texte doit être de style typographique H3.

Pour implémenter ce composable, vous avez besoin d'un composable Image et d'un composable Text. Ils doivent être inclus dans un élément Column, de manière à être positionnés les uns sous les autres.

Recherchez le composable AlignYourBodyElement dans votre code et mettez à jour son contenu avec cette implémentation de base :

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.ui.res.painterResource

@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
   ) {
       Image(
           painter = painterResource(R.drawable.ab1_inversions),
           contentDescription = null
       )
       Text(
           text = stringResource(R.string.ab1_inversions)
       )
   }
}

Remarque :

  • Vous définissez la valeur contentDescription de l'image sur "null", car celle-ci est purement décorative. Le texte affiché sous l'image est suffisamment explicite. L'image n'a donc pas besoin d'une description supplémentaire.
  • Vous utilisez une image et un texte codés en dur. À l'étape suivante, vous allez les déplacer pour utiliser les paramètres fournis dans le composable AlightYourBodyElement afin de les rendre dynamiques.

Voici un aperçu de ce composable :

b9686f83eb73c542.png

Des améliorations doivent y être apportées. On constate tout particulièrement que l'image est trop grande et qu'elle n'a pas la forme d'un cercle. Vous pouvez adapter le composable Image avec les modificateurs size et clip, et le paramètre contentScale.

Le modificateur size adapte le composable à une certaine taille, de la même manière que les modificateurs fillMaxWidth et heightIn que vous avez étudiés à l'étape précédente. Le modificateur clip fonctionne différemment et adapte l'apparence du composable. Vous pouvez le définir sur n'importe quelle Shape. Le contenu du composable est alors tronqué et adapté à cette forme.

L'image doit également être mise à l'échelle de manière correcte. Pour ce faire, vous pouvez utiliser le paramètre contentScale de Image. Plusieurs options s'offrent à vous. Voici les principales :

5f17f07fcd0f1dc.png

Dans ce cas, le type de recadrage utilisé est correct. Après avoir appliqué les modificateurs et le paramètre, votre code devrait se présenter comme suit :

import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier
   ) {
       Image(
           painter = painterResource(R.drawable.ab1_inversions),
           contentDescription = null,
           contentScale = ContentScale.Crop,
           modifier = Modifier
               .size(88.dp)
               .clip(CircleShape)
       )
       Text(
           text = stringResource(R.string.ab1_inversions)
       )
   }
}

Votre conception devrait maintenant ressembler à ceci :

6576ed1e8b1cde30.png

L'étape suivante consiste à aligner le texte horizontalement en définissant l'alignement de Column.

En règle générale, pour aligner des composables dans un conteneur parent, vous devez définir l'alignement de ce conteneur. Ainsi, au lieu de demander à l'élément enfant de se positionner dans son parent, vous indiquez au parent comment aligner ses enfants.

Pour Column, vous déterminez l'alignement horizontal des éléments enfants. Vous disposez des options suivantes :

  • Start
  • CenterHorizontally
  • End

Pour Row, vous définissez l'alignement vertical. Les options sont semblables à celles de Column :

  • Top
  • CenterVertically
  • Bottom

Pour Box, vous combinez les alignements horizontal et vertical. Vous disposez des options suivantes :

  • TopStart
  • TopCenter
  • TopEnd
  • CenterStart
  • Center
  • CenterEnd
  • BottomStart
  • BottomCenter
  • BottomEnd

Tous les éléments enfants du conteneur suivront ce même schéma d'alignement. Vous pouvez ignorer le comportement d'un seul élément enfant en y ajoutant un modificateur align.

Pour cette conception, le texte doit être centré horizontalement. Pour ce faire, définissez le horizontalAlignment de Column pour un centrage horizontal :

import androidx.compose.ui.Alignment
@Composable
fun AlignYourBodyElement(
   modifier: Modifier = Modifier
) {
   Column(
       horizontalAlignment = Alignment.CenterHorizontally,
       modifier = modifier
   ) {
       Image(
           //..
       )
       Text(
           //..
       )
   }
}

Une fois ces éléments implémentés, il ne vous reste plus qu'à apporter quelques petites modifications pour que le composable soit identique à la conception. Essayez de les implémenter vous-même ou reportez-vous au code final si vous rencontrez des difficultés. Effectuez les étapes suivantes :

  • Rendez l'image et le texte dynamiques. Transmettez-les en tant qu'arguments à la fonction modulable. N'oubliez pas de mettre à jour l'aperçu correspondant et de transmettre certaines données codées en dur.
  • Mettez à jour le texte pour qu'il utilise le style typographique approprié.
  • Mettez à jour les espacements de référence de l'élément de texte.

Une fois ces étapes terminées, votre code doit se présenter comme suit :

import androidx.compose.foundation.layout.paddingFromBaseline

@Composable
fun AlignYourBodyElement(
   @DrawableRes drawable: Int,
   @StringRes text: Int,
   modifier: Modifier = Modifier
) {
   Column(
       modifier = modifier,
       horizontalAlignment = Alignment.CenterHorizontally
   ) {
       Image(
           painter = painterResource(drawable),
           contentDescription = null,
           contentScale = ContentScale.Crop,
           modifier = Modifier
               .size(88.dp)
               .clip(CircleShape)
       )
       Text(
           text = stringResource(text),
           style = MaterialTheme.typography.h3,
           modifier = Modifier.paddingFromBaseline(
               top = 24.dp, bottom = 8.dp
           )
       )
   }
}

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun AlignYourBodyElementPreview() {
   MySootheTheme {
       AlignYourBodyElement(
           text = R.string.ab1_inversions,
           drawable = R.drawable.ab1_inversions,
           modifier = Modifier.padding(8.dp)
       )
   }
}

6. Fiche des collections préférées : composant Surface de Material

Le prochain composable à implémenter est, d'une certaine façon, semblable à l'élément "Align the body". Voici la conception, avec la version annotée :

71fcfc487ef8c02a.png

f2f4fb696389ba4f.png

Dans le cas présent, la taille réelle du composable est indiquée. Comme vous pouvez le voir, le style du texte devrait à nouveau être H3.

Ce conteneur a une couleur d'arrière-plan différente de celle de l'ensemble de l'écran. Il présente également des angles arrondis. Étant donné que le concepteur n'a pas précisé de couleur, on peut supposer qu'elle sera définie par le thème. Pour ce type de conteneur, nous utilisons le composable Surface de Material.

Vous pouvez adapter Surface à vos besoins en définissant ses paramètres et son modificateur. Dans ce cas, les angles de la surface doivent être arrondis. Pour ce faire, vous pouvez utiliser le paramètre shape. Au lieu de définir la forme sur Shape, comme pour l'image de l'étape précédente, vous allez utiliser une valeur provenant de notre thème Material.

Voyons le résultat :

import androidx.compose.foundation.layout.Row
import androidx.compose.material.Surface

@Composable
fun FavoriteCollectionCard(
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row {
           Image(
               painter = painterResource(R.drawable.fc2_nature_meditations),
               contentDescription = null
           )
           Text(
               text = stringResource(R.string.fc2_nature_meditations)
           )
       }
   }
}

Et observons un aperçu de cette implémentation :

5584e459f9838f8b.png

Mettez ensuite en pratique les leçons apprises à l'étape précédente. Définissez la taille de l'image et recadrez-la dans son conteneur. Définissez la largeur de Row et alignez ses éléments enfants verticalement. Essayez d'implémenter ces modifications vous-même avant de regarder le code de la solution.

Votre code devrait maintenant se présenter comme suit :

import androidx.compose.foundation.layout.width

@Composable
fun FavoriteCollectionCard(
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier.width(192.dp)
       ) {
           Image(
               painter = painterResource(R.drawable.fc2_nature_meditations),
               contentDescription = null,
               contentScale = ContentScale.Crop,
               modifier = Modifier.size(56.dp)
           )
           Text(
               text = stringResource(R.string.fc2_nature_meditations)
           )
       }
   }
}

L'aperçu devrait maintenant ressembler à ceci :

e0afeb1658a6d82a.png

Pour terminer ce composable, procédez comme suit :

  • Rendez l'image et le texte dynamiques. Transmettez-les en tant qu'arguments à la fonction modulable.
  • Mettez à jour le texte pour qu'il utilise le style typographique approprié.
  • Modifiez l'espacement entre l'image et le texte.

Vous devriez obtenir un résultat final semblable à ceci :

@Composable
fun FavoriteCollectionCard(
   @DrawableRes drawable: Int,
   @StringRes text: Int,
   modifier: Modifier = Modifier
) {
   Surface(
       shape = MaterialTheme.shapes.small,
       modifier = modifier
   ) {
       Row(
           verticalAlignment = Alignment.CenterVertically,
           modifier = Modifier.width(192.dp)
       ) {
           Image(
               painter = painterResource(drawable),
               contentDescription = null,
               contentScale = ContentScale.Crop,
               modifier = Modifier.size(56.dp)
           )
           Text(
               text = stringResource(text),
               style = MaterialTheme.typography.h3,
               modifier = Modifier.padding(horizontal = 16.dp)
           )
       }
   }
}

//..

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun FavoriteCollectionCardPreview() {
   MySootheTheme {
       FavoriteCollectionCard(
           text = R.string.fc2_nature_meditations,
           drawable = R.drawable.fc2_nature_meditations,
           modifier = Modifier.padding(8.dp)
       )
   }
}

7. Ligne "Align your body" : dispositions

Maintenant que vous avez créé les composables de base affichés à l'écran, vous pouvez commencer à créer les différentes sections.

Commencez par la ligne déroulante "Align your body".

25089e1f3e5eab4e.gif

Voici la conception de révision de ce composant :

9d943fabcb5ae632.png

Pour rappel, un bloc de la grille représente 8 dp. Dans cette conception, il y a donc un espace de 16 dp avant le premier élément et après le dernier élément de la ligne. Il y a 8 dp d'espacement entre chaque élément.

Dans Compose, vous pouvez implémenter une ligne à faire défiler comme ceci à l'aide du composable LazyRow. La documentation sur les listes contient beaucoup plus d'informations sur les listes différées telles que LazyRow et LazyColumn. Pour cet atelier de programmation, il suffit de savoir que LazyRow effectue uniquement le rendu des éléments affichés à l'écran, et non de tous les éléments en même temps. Cela contribue à préserver les performances de votre application.

Commencez par une implémentation de base de ce LazyRow :

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

Comme vous pouvez le voir, les éléments enfants d'un LazyRow ne sont pas des composables. Au lieu de cela, vous utilisez la DSL de liste différée qui fournit des méthodes telles que item et items qui émettent des composables sous la forme d'éléments de liste. Pour chaque élément du alignYourBodyData fourni, vous émettez un composable AlignYourBodyElement que vous avez implémenté précédemment.

Voici comment cela se présente à l'écran :

b88f30efe9067f53.png

Il manque toujours les espacements que nous avons vus dans la conception de révision. Pour les implémenter, vous devez vous renseigner sur les dispositions.

À l'étape précédente, vous avez découvert les alignements, lesquels servent à aligner les éléments enfants d'un conteneur sur l'axe transversal. Pour Column, l'axe transversal est l'axe horizontal, tandis que pour Row, il s'agit de l'axe vertical.

Toutefois, nous pouvons également déterminer le positionnement des composables enfants sur l'axe principal d'un conteneur (horizontalement pour Row, verticalement pour Column).

Pour Row, vous pouvez choisir les dispositions suivantes :

c1e6c40e30136af2.gif

Et pour Column :

df69881d07b064d0.gif

En plus de ces dispositions, vous pouvez utiliser la méthode Arrangement.spacedBy() pour ajouter un espace fixe entre chaque composable enfant.

Dans cet exemple, la méthode spacedBy est celle que vous devez utiliser, car vous souhaitez insérer un espacement de 8 dp entre chaque élément de LazyRow.

import androidx.compose.foundation.layout.Arrangement

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

La conception se présente maintenant comme ceci :

c29a8ee73f218868.png

Vous devez également ajouter une marge intérieure sur les côtés de LazyRow. Dans ce cas, l'ajout d'un simple modificateur de marge intérieure ne suffit pas. Essayez d'ajouter une marge intérieure à LazyRow et observez son comportement :

6b3f390040e2b7fd.gif

Comme vous pouvez le voir, lors du défilement, le premier et le dernier élément visible sont tronqués des deux côtés de l'écran.

Pour conserver la même marge intérieure, tout en faisant défiler votre contenu dans les limites de la liste parente sans la couper, toutes les listes fournissent un paramètre appelé contentPadding.

@Composable
fun AlignYourBodyRow(
   modifier: Modifier = Modifier
) {
   LazyRow(
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       contentPadding = PaddingValues(horizontal = 16.dp),
       modifier = modifier
   ) {
       items(alignYourBodyData) { item ->
           AlignYourBodyElement(item.drawable, item.text)
       }
   }
}

8. Grille de collections préférées : grilles différées

La prochaine section à implémenter est la partie "Favorite collections" (Collections préférées) de l'écran. Ce composable a besoin d'une grille et non d'une seule ligne :

4378867d758590ae.gif

Vous pouvez implémenter cette section de la même manière que dans la section précédente, en créant LazyRow et en laissant chaque élément contenir Column avec deux instances FavoriteCollectionCard. Cependant, au cours de cette étape, vous allez utiliser la fonction LazyHorizontalGrid qui permet de mieux mapper les éléments aux éléments de la grille.

Commencez par une implémentation simple de la grille avec deux lignes fixes :

import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.items

@Composable
fun FavoriteCollectionsGrid(
   modifier: Modifier = Modifier
) {
   LazyHorizontalGrid(
       rows = GridCells.Fixed(2),
       modifier = modifier
   ) {
       items(favoriteCollectionsData) { item ->
           FavoriteCollectionCard(item.drawable, item.text)
       }
   }
}

Comme vous pouvez le voir, vous avez simplement remplacé le composable LazyRow de l'étape précédente par LazyHorizontalGrid.

Cependant, vous n'obtiendrez pas encore le résultat correct :

e51beb5c00457902.png

La grille occupe autant d'espace que son élément parent, ce qui signifie que les fiches des collections préférées sont beaucoup trop étirées verticalement. Adaptez le composable de sorte que la taille des cellules de grille et leur espacement soient corrects.

Vous devriez obtenir le résultat suivant :

@Composable
fun FavoriteCollectionsGrid(
   modifier: Modifier = Modifier
) {
   LazyHorizontalGrid(
       rows = GridCells.Fixed(2),
       contentPadding = PaddingValues(horizontal = 16.dp),
       horizontalArrangement = Arrangement.spacedBy(8.dp),
       verticalArrangement = Arrangement.spacedBy(8.dp),
       modifier = modifier.height(120.dp)
   ) {
       items(favoriteCollectionsData) { item ->
           FavoriteCollectionCard(
               drawable = item.drawable,
               text = item.text,
               modifier = Modifier.height(56.dp)
           )
       }
   }
}

9. Section d'accueil : API d'emplacement

L'écran d'accueil de MySoothe comporte plusieurs sections qui suivent le même schéma. Elles ont toutes un titre, ainsi que du contenu qui varie en fonction de la section. Voici la conception que nous souhaitons implémenter :

2c0bc456d50bb078.png

Comme vous pouvez le voir, chaque section comporte un titre et un emplacement. Le titre est associé à des informations sur le style et l'espacement. L'emplacement peut être rempli de manière dynamique avec un contenu différent en fonction de la section.

Pour implémenter ce conteneur de section flexible, vous utilisez ce que l'on appelle des API d'emplacement. Avant l'implémentation, lisez la section traitant des mises en page basées sur les emplacements sur la page de documentation. Vous comprendrez ainsi ce qu'est une mise en page basée sur les emplacements et comment utiliser les API d'emplacement pour créer une mise en page de ce type.

Adaptez le composable HomeSection pour qu'il puisse recevoir le titre et le contenu de l'emplacement. Vous devez également adapter l'aperçu associé pour appeler ce composable HomeSection avec le titre et le contenu "Align your body" :

@Composable
fun HomeSection(
   @StringRes title: Int,
   modifier: Modifier = Modifier,
   content: @Composable () -> Unit
) {
   Column(modifier) {
       Text(stringResource(title))
       content()
   }
}

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2)
@Composable
fun HomeSectionPreview() {
   MySootheTheme {
       HomeSection(R.string.align_your_body) {
           AlignYourBodyRow()
       }
   }
}

Vous pouvez utiliser le paramètre content pour l'emplacement du composable. Ainsi, lorsque vous utilisez le composable HomeSection, vous pouvez utiliser un lambda de fin pour remplir l'emplacement du contenu. Lorsqu'un composable fournit plusieurs emplacements à remplir, vous pouvez leur attribuer des noms explicites qui représentent leur fonction dans le conteneur de composables de plus grande taille. Par exemple, l'élément TopAppBar de Material Design fournit les emplacements pour title, navigationIcon et actions.

Voyons à quoi ressemble la section avec cette implémentation :

d824b60e650deeb.png

Le composable Text a besoin d'informations supplémentaires pour être aligné sur la conception. Modifiez-le pour qu'il réponde aux critères suivants :

  • Il s'affiche en majuscules (conseil : pour cela, vous pouvez utiliser la méthode uppercase() de String).
  • Il utilise la typographie H2.
  • Ses marges intérieures correspondent à la conception de révision.

Une fois terminée, votre solution doit se présenter comme suit :

import java.util.*

@Composable
fun HomeSection(
   @StringRes title: Int,
   modifier: Modifier = Modifier,
   content: @Composable () -> Unit
) {
   Column(modifier) {
       Text(
           text = stringResource(title).uppercase(Locale.getDefault()),
           style = MaterialTheme.typography.h2,
           modifier = Modifier
               .paddingFromBaseline(top = 40.dp, bottom = 8.dp)
               .padding(horizontal = 16.dp)
       )
       content()
   }
}

10. Écran d'accueil : défilement

Maintenant que vous avez créé tous les composants principaux, vous pouvez les combiner dans une implémentation en plein écran.

Voici la conception que vous essayez d'implémenter :

a535e10437e9df31.png

Nous plaçons simplement la barre de recherche et les deux sections l'une en dessous de l'autre. Vous devez ajouter un espacement pour que tout soit adapté à la conception. Spacer est un composable que nous n'avons jamais utilisé auparavant. Il nous permet d'ajouter de l'espace dans notre élément Column. Si, au lieu de cela, vous définissez la marge intérieure de Column, vous obtiendrez le même comportement de "coupure" que celui observé précédemment dans la grille des collections préférées.

@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
   Column(modifier) {
       Spacer(Modifier.height(16.dp))
       SearchBar(Modifier.padding(horizontal = 16.dp))
       HomeSection(title = R.string.align_your_body) {
           AlignYourBodyRow()
       }
       HomeSection(title = R.string.favorite_collections) {
           FavoriteCollectionsGrid()
       }
       Spacer(Modifier.height(16.dp))
   }
}

Bien que la conception s'adapte bien à la plupart des tailles d'appareil, il doit être possible de la faire défiler verticalement si l'écran n'est pas assez grand (en mode Paysage, par exemple). Pour cela, vous devez ajouter le comportement de défilement.

Comme nous l'avons vu précédemment, les mises en page différées (Lazy), telles que LazyRow et LazyHorizontalGrid, ajoutent automatiquement le comportement de défilement. Cependant, vous n'avez pas toujours besoin d'une mise en page de ce type. En règle générale, vous utilisez une mise en page différée lorsqu'il y a de nombreux éléments dans une liste ou de grands ensembles de données à charger. De ce fait, l'émission simultanée de tous les éléments aurait un impact sur les performances et ralentirait votre application. Lorsqu'une liste ne contient qu'un nombre limité d'éléments, vous pouvez choisir d'utiliser un élément Column ou Row simple, puis d'ajouter le comportement de défilement manuellement. Pour ce faire, utilisez les modificateurs verticalScroll ou horizontalScroll. Ils nécessitent un élément ScrollState, qui contient l'état actuel du défilement et qui est utilisé pour modifier l'état depuis l'extérieur. Dans le cas présent, vous ne cherchez pas à modifier l'état de défilement. Vous allez donc simplement créer une instance ScrollState persistante à l'aide de rememberScrollState.

Le résultat final doit se présenter comme suit :

import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll

@Composable
fun HomeScreen(modifier: Modifier = Modifier) {
   Column(
       modifier
           .verticalScroll(rememberScrollState())
           .padding(vertical = 16.dp)
   ) {
       SearchBar(Modifier.padding(horizontal = 16.dp))
       HomeSection(title = R.string.align_your_body) {
           AlignYourBodyRow()
       }
       HomeSection(title = R.string.favorite_collections) {
           FavoriteCollectionsGrid()
       }
   }
}

Pour vérifier le comportement de défilement du composable, limitez la hauteur de l'aperçu et exécutez-le dans l'aperçu interactif :

@Preview(showBackground = true, backgroundColor = 0xFFF0EAE2, heightDp = 180)
@Composable
fun ScreenContentPreview() {
   MySootheTheme { HomeScreen() }
}

11. Navigation inférieure : Material

Maintenant que vous avez implémenté le contenu de l'écran, vous êtes prêt à ajouter la décoration de la fenêtre. Dans le cas de MySoothe, une barre de navigation inférieure permet à l'utilisateur de basculer entre différents écrans.

Commencez par implémenter le composable de navigation inférieure proprement dit, puis incluez-le dans votre application.

Observons la conception :

2f42ad2b882885f8.png

Heureusement, vous n'avez pas à implémenter vous-même l'intégralité de ce composable en partant de zéro. Vous pouvez utiliser le composable BottomNavigation qui fait partie de la bibliothèque Material de Compose. Dans le composable BottomNavigation, vous pouvez ajouter un ou plusieurs éléments BottomNavigationItem auxquels un style sera automatiquement appliqué par la bibliothèque Material.

Commencez par une implémentation de base de cette navigation inférieure :

import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Spa

@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
   BottomNavigation(modifier) {
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.Spa,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_home))
           },
           selected = true,
           onClick = {}
       )
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.AccountCircle,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_profile))
           },
           selected = false,
           onClick = {}
       )
   }
}

Voici à quoi ressemble cette implémentation de base :

5bdb7729d75e1a72.png

Vous devez effectuer quelques adaptations stylistiques. Tout d'abord, vous pouvez modifier la couleur d'arrière-plan de la barre de navigation inférieure en définissant son paramètre backgroundColor. Pour cela, vous pouvez utiliser la couleur d'arrière-plan du thème Material. Lorsque vous définissez la couleur de l'arrière-plan, la couleur des icônes et du texte s'adapte automatiquement à la couleur onBackground du thème. Une fois terminée, votre solution doit se présenter comme suit :

@Composable
private fun SootheBottomNavigation(modifier: Modifier = Modifier) {
   BottomNavigation(
       backgroundColor = MaterialTheme.colors.background,
       modifier = modifier
   ) {
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.Spa,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_home))
           },
           selected = true,
           onClick = {}
       )
       BottomNavigationItem(
           icon = {
               Icon(
                   imageVector = Icons.Default.AccountCircle,
                   contentDescription = null
               )
           },
           label = {
               Text(stringResource(R.string.bottom_navigation_profile))
           },
           selected = false,
           onClick = {}
       )
   }
}

12. Application MySoothe : échafaudage (scaffolding)

Au cours de cette dernière étape, vous allez créer l'implémentation plein écran, y compris la barre de navigation inférieure. Utilisez le composable Scaffold de Material Design. Scaffold fournit un composable configurable de niveau supérieur pour les applications qui implémentent Material Design. Il contient des emplacements pour différents concepts Material, dont l'un est la barre inférieure. Dans cette barre inférieure, vous pouvez placer le composable de navigation inférieure que vous avez créé à l'étape précédente.

Implémentez le composable MySootheApp. Il s'agit du composable de niveau supérieur de votre application. Vous devez donc effectuer les opérations suivantes :

  • Appliquer le thème Material MySootheTheme.
  • Ajouter Scaffold.
  • Définir la barre inférieure comme étant votre composable SootheBottomNavigation.
  • Définir le contenu comme étant votre composable HomeScreen.

Vous devriez obtenir le résultat suivant :

import androidx.compose.material.Scaffold

@Composable
fun MySootheApp() {
   MySootheTheme {
       Scaffold(
           bottomBar = { SootheBottomNavigation() }
       ) { padding ->
           HomeScreen(Modifier.padding(padding))
       }
   }
}

Votre implémentation est maintenant terminée. Pour vérifier que votre version a été implémentée au pixel près, vous pouvez télécharger l'image suivante et la comparer à votre propre version d'aperçu.

24ff9efa75f22198.png

13. Félicitations

Félicitations, vous avez terminé cet atelier de programmation au cours duquel vous avez découvert les mises en page dans Compose. En implémentant une conception réelle, vous avez découvert les modificateurs, les alignements, les dispositions, les mises en page différées (Lazy), les API d'emplacement, le défilement et les composants Material Design.

Consultez les autres ateliers de programmation du parcours Compose. Consultez également les exemples de code.

Documentation

Pour en savoir plus et obtenir des conseils à ce sujet, consultez la documentation suivante :