
Nous allons maintenant gérer individuellement la taille et la disposition d’un composant. Nous en profiterons aussi pour apprendre quelques bonnes pratiques pour organiser vos composants de manière autonome et efficace.
Par défaut, les layouts standards enveloppent leurs enfants. Ils s'adaptent à la taille de leurs contenus enfants, ajustant leur hauteur et leur largeur en fonction des dimensions de ces derniers. Cela permet au conteneur de s'adapter parfaitement à son contenu, assurant ainsi que rien ne dépasse ni ne laisse d'espace inutilisé.
Illustrons cela avec un composant qui affiche simplement un titre “Mes Podcasts” et une icône dans un layout de typeRow. Cela donne le code ci-dessous :
@Preview
@Composable
fun DemoLayoutWrapByDefault() {
Row {
Text(
text = "Mes Podcasts",
style = MaterialTheme.typography.titleMedium,
)
Icon(
Icons.Default.Podcasts,
contentDescription = null,
)
}
}Dans cet exemple, laRowparente occupe exactement la largeur et la hauteur nécessaires pour afficher le texte “Mes podcasts” et l'icône associée.

Comment faire pour que laRowparente occupe tout l’espace disponible en largeur, soit l’équivalent de la valeurmatch_parentdans les systèmes de mise en page en XML ?
Pour atteindre cet objectif, nous allons devoir utiliser le ModifierfillMaxWidth()comme ceci :
@Preview
@Composable
fun DemoLayout() {
Row(modifier = Modifier.fillMaxWidth()) {
Text(
text = "Mes Podcasts",
style = MaterialTheme.typography.titleMedium,
)
Icon(
Icons.Default.Podcasts,
contentDescription = null,
)
}
}Voici le résultat associé.

Il existe d'autresModifiersqui influent sur la taille d'un composant. Voici une liste non exhaustive :
Modifier.fillMaxWidth(): pour occuper toute la largeur disponible. Il peut prendre en paramètre une fraction, pour déterminer la proportion de la largeur à occuper.
Modifier.fillMaxHeight() : pour occuper toute la hauteur disponible. Il peut prendre en paramètre une fraction, pour déterminer la proportion de la hauteur à occuper.
Modifier.fillMaxSize(): pour occuper toute la largeur et la hauteur disponibles.
Modifier.size(): pour définir une taille préférentielle.
Modifier.weight(): pour répartir la largeur ou la hauteur d'un composant proportionnellement au poids spécifié avec cette fonction.
Modifier.aspectRatio(): pour contraindre la largeur et la hauteur d'un composant selon un ratio spécifique, garantissant ainsi des proportions fixes indépendamment de l'espace disponible.
Notez que leModifier.weight()est utilisable uniquement au sein d’un layout de typeRowouColumn. Si vous essayez de l’utiliser au sein d’un autre layout, votre code ne compilera pas. Vous vous en rendrez vite compte !
Voici un exemple utilisant certains de cesModifierspour obtenir un rendu plus aéré que l’exemple précédent.
@Preview
@Composable
fun DemoLayout() {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Mes Podcasts",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f),
)
Icon(
Icons.Default.Podcasts,
contentDescription = null,
modifier = Modifier.size(48.dp),
)
}
}Et voici le résultat.

Grâce aux ModifiersModifier.horizontalScroll()etModifier.verticalScroll(), un composant devient défilable lorsque l'espace alloué sur l'écran est plus petit que la taille du contenu qu’il contient.
Comment cela s’applique à notre application, BestPodcast ?
Eh bien justement nous en avons besoin pour le composantCategoryFilterque nous avons illustré dans “Organisez vos composants avec les layouts de base”. Rappelez-vous, ce composant utilise un layout de typeRowpour afficher plusieurs boutons permettant de filtrer la liste des podcasts par catégorie. Nous avons illustré son utilisation avec uniquement trois catégories et visuellement tout semblait correct. Cependant, si vous ajoutez plus d’éléments, comme dans la prévisualisation suivante, vous verrez que le rendu visuel n’est plus correct. Voici le code pour obtenir la prévisualisation.
@Preview
@Composable
fun CategoryFilterPreview() {
BestPodcastTheme {
val podcastCategories =
listOf(
Category(1, "Technologie"),
Category(2, "Ecologie"),
Category(3, "Santé"),
Category(id = 4, "Sport"),
Category(id = 5, "Education"),
)
val selected: MutableState<Int?> = remember { mutableStateOf(null) }
CategoryFilter(
categories = podcastCategories,
onCategoryClicked = {
if (selected.value == it) {
selected.value = null
} else {
selected.value = it
}
},
selectedId = selected.value,
)
}
}Et voici le résultat.

Pour corriger cela, il suffit de rendre défilable horizontalement le layoutRowparent en appliquant leModifier.horizontalScroll(state: ScrollState). Le code du composantCategoryFilterdevient alors :
@Composable
fun CategoryFilter(
categories: List<Category>,
selectedId: Int?,
onCategoryClicked: (Int) -> Unit,
) {
val scrollState = rememberScrollState()
Row(
modifier =
Modifier
.fillMaxWidth()
.selectableGroup()
.horizontalScroll(scrollState),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
categories.forEach { category ->
/*...*/
}
}
}Notez que le paramètre state de typeScrollStateque vous voyez ici permet de connaître la position de défilement et fournit des fonctions commeScrollState.animateScrollTo()etScrollState.scroll(). Pour le créer,rememberScrollState()doit être utilisé, mémorisant ainsi l’état de défilement du composant au-delà des recompositions. Et voici le rendu final.
Maintenant que vous en savez un peu plus sur lesModifiersà adopter pour mettre en page les composants de vos écrans, il est temps que je vous partage des bonnes pratiques pour vous faciliter la tâche lors de la conception et la maintenance de vos composants.
Nous avons observé que les layoutsRow,ColumnetBoxdisposent d’un paramètre pour ajuster l’alignement de leurs enfants. Mais il est parfois nécessaire de personnaliser l'alignement de certains éléments unitairement. C’est ce que nous permet de faire leModifier.align()qui est accessible au sein de chacun de ces layouts.
Comment cela s’applique à notre application, BestPodcast ?
Chaque podcast a un titre et une catégorie associée. Pour pouvoir aligner uniquement le texte contenant la catégorie à droite, nous allons utiliser sur ce texte le modifierModifier.align()accessible au sein du layoutColumn, en lui appliquant la valeurAlignment.End. Cela donne le code ci-dessous :
@Composable
fun OtherPodcastItem(
podcast: Podcast,
) {
Column(
modifier = Modifier.width(200.dp),
verticalArrangement = Arrangement.SpaceAround,
) {
AsyncImage(
model = podcast.logoUrl,
contentDescription = null,
placeholder = painterResource(id = R.drawable.placeholder),
modifier = Modifier.fillMaxWidth().aspectRatio(1f)
)
Text(
text = podcast.category.text,
style = MaterialTheme.typography.labelLarge,
fontStyle = FontStyle.Italic,
modifier = Modifier.align(Alignment.End),
color = MaterialTheme.colorScheme.tertiary,
)
Text(
text = podcast.title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
)
}
}Et voici le résultat final.

Si nous ne pouvions pas concevoir des composants réutilisables dans une application, cela entraînerait plusieurs problèmes ! En tant que développeurs et développeuses, nous serions contraints de dupliquer les composants chaque fois qu’on en a besoin dans des contextes différents. Cela augmenterait considérablement notre charge de travail et le risque d'erreurs.
Alors pour éviter cela, la pratique la plus courante est d’exposer un paramètremodifieroptionnel parmi les paramètres des composants.
Cela les rendra flexibles et réutilisables dans divers contextes et facilitera ainsi leur évolution future.
Comment cela s’applique à notre application, BestPodcast ?
Rappelez-vous du composantOtherPodcastItemutilisé précédemment. Celui-ci donne des informations sur un podcast dans un contenu affiché dans un layout de typeColumn.
Notre objectif est d’obtenir ce rendu visuel dans notre application Android.

Ce design a les particularités suivantes :
La couleur de fond doit êtreprimaryContainerpour la suggestion de gauche, ettertiaryContainerpour celle de droite.
Les deux suggestions ont une ombre donnant l’impression d’être surélevées et elles sont cliquables.
Les deux suggestions occupent chacune la moitié de l’écran, en largeur.
La hauteur des deux suggestions dépend de la plus grande des deux. Si la suggestion du podcast “Le numérique vert” a besoin de plus de place pour s’afficher sur deux lignes, alors la suggestion du podcast “Changements” doit faire la même hauteur.
Pour rappel, voici maintenant le rendu et le code actuel de notre composantOtherPodcastItem.

En l’état, le code associé ne nous permet pas d’obtenir le rendu que nous souhaitons car :
La largeur fixe de200.dpne permet pas d’adapter le composant à la moitié de la largeur de l’écran.
Il n’y a pas d’ombre, le composant n’est pas cliquable et la couleur de fond ne peut pas être personnalisée.
@Composable
fun OtherPodcastItem(
podcast: Podcast,
) {
Column(
verticalArrangement = Arrangement.SpaceAround,
modifier = Modifier.width(200.dp)
) {
AsyncImage(
model = podcast.logoUrl,
contentDescription = null,
placeholder = painterResource(id = R.drawable.placeholder),
modifier = Modifier.fillMaxWidth().aspectRatio(1f),
)
Text(
text = stringResource(id = podcast.category,
style = MaterialTheme.typography.labelLarge,
fontStyle = FontStyle.Italic,
modifier = Modifier.align(Alignment.End),
color = MaterialTheme.colorScheme.tertiary,
)
Text(
text = podcast.title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
)
}
}Alors pourquoi ne pas passer tout simplement ces paramètres de style en paramètre de la fonctionOtherPodcastItem?
Nous pourrions effectivement revoir la signature de notre fonction pour inclure ces critères comme ceci :
@Composable
fun OtherPodcastItem(
podcast: Podcast,
width: Dp,
backgroundColor: Color,
elevation: Dp,
onClick: () -> Unit
) {
/*...*/
}Pour résoudre ce problème et faciliter les futures évolutions de notre composant, nous allons :
Ajouter un paramètre modifieroptionnel dans la signature du composantOtherPodcastItem.
Appliquer ce paramètre au premier conteneur du composant.
Ne pas imposer de tailles fixes au layout parent du composant. Cela permet ainsi à notre composant de s’adapter au contexte où il est utilisé, et de se répartir selon l’espace dont il dispose.
Voici la mise à jour du code de la signature de notre composant.
@Composable
fun OtherPodcastItem(
podcast: Podcast,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.SpaceAround,
) {
/*...*/
}
}Maintenant que sa signature est à jour, nous pouvons personnaliser le rendu du composantOtherPodcastItemcomme ce qui suit :
@Preview
@Composable
fun TwoSuggestionsRowPreview() {
BestPodcastTheme {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.padding(vertical = 8.dp).height(IntrinsicSize.Max),
) {
OtherPodcastItem(
podcast = PodcastFactory.makePodcasts()[0],
modifier =
Modifier
.fillMaxHeight()
.weight(1f)
.shadow(4.dp)
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(4.dp)
.clickable { /* TODO */ },
)
OtherPodcastItem(
podcast = PodcastFactory.makePodcasts()[1],
modifier =
Modifier
.fillMaxHeight()
.weight(1f)
.shadow(4.dp)
.background(MaterialTheme.colorScheme.tertiaryContainer)
.padding(4.dp)
.clickable { /* TODO */ },
)
}
}
}Quelques remarques intéressantes concernant cet extrait de code :
Le modifier.weight(1f)appliqué à chaqueOtherPodcastItempermet d’affecter un poids de 1 à chaque composant, les faisant occuper chacun la moitié de l’écran en largeur.
Le modifiershadowajoute un effet d’élévation.
Le modifierclickablerend cliquable le composant en entier.
Le modifierfillMaxHeight()appliqué sur chaque OtherPodcastItem en combinaison avec le modifier.height(IntrinsicSize.Max`)sur laRowindique à Jetpack Compose que la hauteur des deuxOtherPodcastItemsera définie par celui qui a la plus grande hauteur. Le podcast “Le numérique vert” passe sur deux lignes, la suggestion “Changements” sera aussi haute. Sans ce modifier, nous aurions le rendu suivant :

Voici une vidéo qui récapitule les principales étapes pour optimiser l'organisation de vos composants pour des interfaces plus harmonieuses.
Vous venez de découvrir des notions fondamentales sur la conception de composants réutilisables. Il est temps maintenant pour vous :
de refactoriser le composantPodcastItemet l'ensemble des composants que vous avez développés jusqu'à présent en appliquant ces nouvelles connaissances ;
concevoir un nouveau composantSuggestioncorrespondant à la maquette que Mehdi, le graphiste de votre équipe, vous a fourni. Il a utilisé pour cela l’outil Excalidraw. Le composant sera utilisé plus tard dans l’applicationBestPodcast .

1. Refactorisez l’ensemble des composants conçus jusqu'à maintenant dans BestPodcast en appliquant la bonne pratique qui consiste à exposer un paramètre optionnel de typeModifierdans leur signature et à l’appliquer au premier conteneur.
2. Avant de vous lancer tête baissée dans l’implémentation du composantSuggestionillustré ci-dessus, prenez le temps d’identifier :
Les états dont dépend le composant afin de définir la signature de sa fonction.
Les layouts à utiliser et leurs imbrications.
Les modifiers à appliquer.
Pour ce faire, vous pouvez, sur la base de ce que Mehdi vous a fourni, dessiner un simple croquis ou utiliser un outil numérique comme Excalidraw.
3. Développez le composantSuggestion. Vous aurez besoin de la ressourcesound_wave.xmlpour l’image représentant une onde sonore positionnée derrière le logo.
Votre projet sur Android Studio doit être conforme aux consignes ci-dessus.
Les layoutsRow,ColumnetBox enveloppent leurs enfants par défaut.
Les modifiersfillMaxWidth(),fillMaxHeight(),fillMaxSize() ,size(),weight(), ou encoreaspectRatio()permettent d’ajuster la taille d’un composant ou d’un layout.
Les layoutsRow,ColumnetBox incluent une méthode Modifier.align()qui permet de personnaliser individuellement l'alignement de leurs éléments enfants.
Exposer un paramètremodifieroptionnel dans la signature de votre composant permet de le rendre personnalisable en termes de style et de disposition.
Pour permettre au composant de se répartir selon l'espace dont il dispose, il ne faut pas imposer de tailles fixes à son layout parent.
Maintenant que nous avons soigneusement perfectionné la représentation d’un podcast, il est temps de les afficher tous ensemble dans une liste.