Mise en page standard
Introduction
Une fonction Composable peut contenir plusieurs éléments. Si nous ne précisons pas comment agencer ces éléments entre eux, Compose va superposer ces éléments les uns au-dessus des autres.
Prenons par exemple la fonction Composable suivante :
@Composable
fun ComponentWithoutLayout() {
Text("Nouvelle conversation")
Text("Créer un groupe")
}
Son rendu visuel sera alors :
Prévisualisation du composant ComponentWithoutLayout
C’est pourquoi, pour effectuer une mise en page standard, il convient d’utiliser les quatre layouts fournis par Compose UI : Row, Column, Box et BoxWith Constraints. Ils permettent respectivement de positionner plusieurs éléments de manière horizontale, verticale, en superposition sans contraintes ou en superposition selon l’espace disponible.
Illustration de l’agencement des enfants des composants Column, Row, Box et BoxWithConstraints
Nous allons apprendre à utiliser ces différents layouts dans ce chapitre.
Le processus de mise en page
Avant de découvrir comment utiliser les différents layouts fournis par Compose UI, prenons un peu de recul et voyons comment se déroule le processus de mise en page.
Nous avons vu dans le chapitre La genèse de Jetpack Compose que ce processus se déroule en trois phases :
-
La phase de composition. Durant celle-ci, les fonctions Composables de notre code vont être exécutées. Ces fonctions vont émettre des éléments d’interface, générant une description de l’interface sous forme d’arbre.
-
La phase de layout. Durant cette phase, l’arbre généré précédemment va être parcouru et chaque élément d’interface présent dans celui-ci va être mesuré puis positionné sur l’écran.
-
La phase de drawing. Durant cette dernière phase, l’arbre va être parcouru de nouveau et chaque élément d’interface va être dessiné.
Phases de transformation d’un état en un élément visuel à l’écran
Nous avons vu comment fonctionne la phase de composition dans le chapitre La genèse de Jetpack Compose, section La recomposition. Nous allons maintenant analyser comment fonctionne la phase de layout.
La phase de layout
La phase de layout se découpe en réalité en deux processus qui s’entremêlent :
-
la mesure, c’est-à-dire la détermination, pour chaque nœud de l’arbre, de sa largeur et de sa hauteur
-
le positionnement, c’est-à-dire la détermination, pour chaque nœud de l’arbre, de ses coordonnées x et y sur l’écran en 2D
Illustration des phases de mesure et de positionnement dans le processus de rendu d’une interface
Le processus...
Mise en page en ligne ou en colonne
Le layout Row
Le layout Row permet d’aligner horizontalement plusieurs composants. Voici sa signature :
@Composable
inline fun Row(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal =
Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable RowScope.() -> Unit
) {
/* ... */
}
Focalisons notre attention sur les paramètres horizontalArrangement et verticalAlignement. Ils permettent de modifier la disposition des enfants d’un layout de type Row. En particulier :
-
horizontalArrangement permet de définir la disposition horizontale de ses enfants. Plusieurs valeurs prédéfinies existent au sein de l’objet Arrangement.Horizontal. Celles-ci sont : Start, End, Center, SpaceBetween, SpaceAround, SpaceEvenly, mais également la fonction spacedBy().
-
verticalAlignment détermine l’alignement vertical des composants enfants. Plusieurs valeurs prédéfinies existent au sein de l’objet Aligment.Vertical. Celles-ci sont : Top, Bottom et CenterVertically.
Ces dispositions sont illustrées ci-dessous, dans les figures « Illustration de différentes valeurs possibles pour le paramètre horizontalArrangement » et « Illustration de différents alignements possibles pour le paramètre verticalAlignment ».
Illustration de différentes valeurs possibles pour le paramètre horizontalArrangement
Illustration de différents alignements possibles pour le paramètre verticalAlignment
Illustrons l’utilisation du layout Row en développant...
Modifiers affectant la mise en page
Nous avons vu comment mettre en page globalement les enfants des layouts Row et Column. Cependant, il est parfois nécessaire d’ajuster la mise en page des enfant d’un layout de manière unitaire, par exemple aligner verticalement le premier enfant à gauche, le second au centre, et le dernier à droite. Pour cela, nous allons utiliser des Modifiers. Avant de découvrir les autres layouts, ouvrons donc une parenthèse pour découvrir certains de ces Modifiers et voyons comment ils affectent la mise en page.
La taille
Par défaut, un layout standard enveloppe ses enfants. Il est pourtant possible d’affecter à chaque composant une taille spécifique. Voici une liste non exhaustive des Modifiers permettant de modifier la taille d’un composant.
Définir une taille préférentielle
Pour définir une taille préférentielle, il existe par exemple le Modifier Modifier.size().
Row(
modifier = Modifier.size(width = 200.dp, height = 300.dp)
) {
/*...*/
}
À noter l’importance de la notion de préférentielle dans la définition de cette taille. En effet, selon les contraintes de positionnement transmises par les parents du composant affecté par ce Modifier, il se peut que cette nouvelle contrainte de taille ne soit pas respectée, par exemple si le parent a une taille plus petite que celle renseignée. Pour indiquer à Compose UI d’utiliser une taille précise sans prendre en compte les contraintes environnantes, il existe le Modifier Modifier.requiredSize(/*...*/). Évidemment, c’est un Modifier à utiliser avec précaution.
Occuper l’espace disponible
Il existe plusieurs Modifiers qui permettent d’indiquer à...
Mise en page en superposition
Les paramètres du layout Box
Le layout qui permet de superposer des composants au sein de Compose UI s’appelle Box. Voici sa signature :
inline fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
/*...*/
}
Focalisons notre attention sur les paramètres contentAlignement et propagateMinConstraints de ce layout :
-
contentAlignement permet de définir l’alignement de l’ensemble de ses enfants. Plusieurs valeurs prédéfinies existent au sein de l’objet Alignment. Celles-ci sont : Center, CenterStart, CenterEnd, TopStart, TopCenter, TopEnd, BottomStart, BottomCenter et BottomEnd. Voici une illustration du positionnement de ces différents alignements :
Illustration des alignements au sein du layout Box
-
propagateMinConstraints permet de définir si les contraintes de taille minimale appliquées au layout doivent être propagées à ses enfants. Illustrons cela avec l’exemple suivant :
@Preview
@Composable
fun PreviewBoxPropagateConstraints() {
Box(
modifier = Modifier.defaultMinSize(
minHeight = 100.dp,
minWidth = 400.dp
),
propagateMinConstraints = true
) {
Box(modifier...
Mise en page selon l’espace disponible avec BoxWithConstraints
Il existe un autre layout appelé BoxWithConstraints. Il permet de construire des composants responsives, c’est-à-dire dont le rendu visuel s’adapte selon l’espace disponible. Grâce à ce layout, nous allons pouvoir effectuer une mise en page différente et ainsi obtenir des composants capables de s’adapter selon :
-
la résolution de l’appareil,
-
le type d’appareil : smartphone standard, pliant, tablette, etc.
-
l’orientation de l’appareil,
-
l’espace disponible. En effet, selon où un composant est utilisé, l’espace dont il dispose peut-être différent. Par exemple : en plein écran, dans une fenêtre de dialogue, dans la barre de navigation, au sein d’une liste, etc.
Pour pouvoir effectuer cela, le layout BoxWithConstraints donne accès via son scope aux informations suivantes :
-
La taille minimale disponible en dp : minWidth pour la largeur et minHeight pour la hauteur.
-
La taille maximale disponible en dp : maxWidth pour la largeur et maxHeight pour la hauteur.
-
Un objet de type Constraints contenant notamment les contraintes de tailles appliquées à ce layout en pixels.
En complément de ces informations, comme son nom l’indique, BoxWithConstraints se comporte comme le layout Box et il dispose des mêmes paramètres en entrée.
Capture d’écran issue d’Android Studio illustrant l’accès au scope BoxWithConstraintsScope
Cas d’usage
Utilisons le layout BoxWithConstraints pour adapter le rendu du composant UserBadgeWithLastConnection utilisé précédemment. En particulier, lorsque l’espace disponible en largeur est inférieur à 600 dp, faisons les ajustements suivants :
-
Affichons...
Les mesures intrinsèques
Introduction
Il arrive qu’un composant ait besoin de connaître la taille d’autres composants pour définir sa propre taille. Pour illustrer cette situation, prenons l’exemple d’un composant ayant les spécifications suivantes :
-
Trois textes plus ou moins longs au sein d’une Column.
-
Tous les textes ont une couleur de fond occupant autant d’espace en largeur que le plus large des trois textes.
Voici un aperçu de ce composant :
Illustration du composant contenant trois textes
Nous serions tentés de trouver ici un moyen de récupérer la largeur du texte le plus large et d’appliquer cette largeur à chaque enfant du composant, Column comme ceci :
@Composable
fun ColumnWithThreeTextsNotWorkingV1() {
Column {
val widestWidth = // TODO ?
Text(
"Texte court",
modifier = Modifier
.width(widestWidth)
.background(Color.Gray)
)
Text(
"Texte le plus long définissant la largeur de ses voisins",
color = Color.White,
modifier...
Bonnes pratiques d’accessibilité
Au-delà de veiller à l’accessibilité de chaque composant unitairement, lorsque nous réalisons une mise en page en regroupant plusieurs composants, il est important de veiller à ce que dans son ensemble celui-ci soit utilisable. À notre échelle de développeur ou de développeuse, nous pouvons contribuer à cela en appliquant les bonnes pratiques suivantes :
-
S’assurer que le contenu peut être redimensionné jusqu’à 200 % sans dégradation de lecture.
-
Utiliser le rôle sémantique Heading pour faciliter la navigation avec un lecteur d’écran.
-
Regrouper les enfants d’un layout pour faciliter la navigation avec un lecteur d’écran.
Examinons en détail ces bonnes pratiques, leur raison d’existence et comment les appliquer.
Redimensionnement sans dégradation jusqu’à 200 %
Dans les paramètres d’affichage du système Android, un utilisateur peut augmenter le niveau de zoom global via deux paramètres : la taille de la police et la taille d’affichage. Ce dernier paramètre modifie à la fois la taille de la police et également la taille des éléments à l’écran.
À partir d’un certain niveau de zoom, la mise en page globale peut être dégradée. En tant que développeur ou développeuse, il est important de s’assurer que l’information reste lisible et que les fonctionnalités soient toujours accessibles. Concrètement, il faut s’assurer que les composants ne sont pas superposés ou tronqués.
Cette bonne pratique correspond à la règle 1.4.4 Redimensionnement du texte dans le référentiel WCAG.
Exemple
Afin d’illustrer...