Blog ENI : Toute la veille numérique !
En raison d'une opération de maintenance, le site Editions ENI sera inaccessible le mardi 10 décembre, en début de journée. Nous vous invitons à anticiper vos achats. Nous nous excusons pour la gêne occasionnée
En raison d'une opération de maintenance, le site Editions ENI sera inaccessible le mardi 10 décembre, en début de journée. Nous vous invitons à anticiper vos achats. Nous nous excusons pour la gêne occasionnée
  1. Livres et vidéos
  2. Jetpack Compose
  3. Mise en page standard
Extrait - Jetpack Compose Développez des interfaces accessibles et modernes pour Android
Extraits du livre
Jetpack Compose Développez des interfaces accessibles et modernes pour Android Revenir à la page d'achat du livre

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 :

images/06EI01.png

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.

images/06EI02.png

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é.

images/06EI03.png

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

images/06EI04.png

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 ».

images/06EI08.png

Illustration de différentes valeurs possibles pour le paramètre horizontalArrangement 

images/06EI09.png

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.

Images/06EI45.png

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 :

images/06EI23.png

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.

images/06EI31.png

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 :

images/06EI36.png

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...