Les listes
Introduction
Au sein d’une application, il est fréquent de devoir afficher une liste contenant beaucoup d’éléments. Dans ces cas-là, utiliser un layout basique comme une Row ou une Column peut engendrer de mauvaises performances. En effet, ces layouts vont composer, mesurer et dessiner tous les éléments de la liste en même temps, même ceux qui ne sont pas encore visibles à l’écran. C’est pour gérer de manière optimisée ce genre de situation que Compose UI fournit, à l’heure où ce livre est écrit, six fonctions Composables : LazyColumn, LazyRow, LazyVerticalGrid, LazyHorizontalGrid, ainsi que LazyVerticalStaggeredGrid et LazyHorizontalStaggeredGrid. Ces fonctions permettent de réaliser des listes ou des grilles, comme illustré dans la figure suivante. Le rendu des éléments contenus dans ces six fonctions est optimisé puisque seuls les éléments qui sont visibles à l’écran sont composés, mesurés et dessinés.
Lazy layouts
Dans la littérature, le terme « lazy layouts » est utilisé pour désigner ces six fonctions Composables.
Avant de découvrir en profondeur le fonctionnement de ces six layouts, illustrons à quel point avec Compose UI, implémenter une telle liste optimisée demande très peu de ligne de code. Dans cet exemple, nous utilisons LazyColumn pour représenter la liste des conversations au sein de notre application de messagerie :
val conversations by homeViewModel.conversations.collectAsState()
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(conversations) { conversation ->
ConversationItem(conversation) ...
Définition du contenu via un DSL
Par rapport aux layouts standard que nous avons étudiés dans le chapitre Mise en page standard, les lazy layouts se différencient en mettant à disposition un Domain Specific Language ou DSL permettant de faciliter la déclaration de leur contenu. Comme dans l’exemple en introduction avec l’usage de la fonction items{} à l’intérieur du composant LazyColumn.
DSL : définition
Ouvrons une parenthèse pour définir ce qu’est un DSL. Elle désigne un langage qui est spécifique pour une partie d’une application. Un DSL est conçu pour rendre le code plus lisible et plus compréhensible, notamment en masquant l’implémentation interne et en supprimant le code redondant. En tant que développeurs et développeuses, nous utilisons quotidiennement un DSL dans nos projets Android, comme le code que nous écrivons dans nos scripts gradle. Nous utilisons dans ce cas-là une DSL écrite en langage Groovy ou Kotlin. Si nous reprenons par exemple l’extrait suivant du fichier build.gradle d’une application :
android {
compileSdk 33
defaultConfig {
applicationId "com.compose.livre"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
} ...
LazyColumn et LazyRow
Introduction
Après avoir découvert les différentes fonctions du DSL permettant de peupler une liste, attardons-nous maintenant dans cette section sur le fonctionnement global des layouts LazyColumn et LazyRow.
Paramètres
Analysons les différents paramètres des fonctions LazyRow et LazyColumn afin de voir comment personnaliser une liste. Pour cela, regardons les signatures de ces deux fonctions :
@Composable
fun LazyRow(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
horizontalArrangement: Arrangement.Horizontal =
if (!reverseLayout)
Arrangement.Start
else
Arrangement.End,
verticalAlignment: Alignment.Vertical = Alignment.Top,
flingBehavior: FlingBehavior =
ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyListScope.() -> Unit
) {
/*...*/
}
@Composable
fun LazyColumn(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical...
Grilles
Paramètres de LazyVerticalGrid et LazyHorizontalGrid
Les layouts LazyVerticalGrid et LazyHorizontalGrid permettent d’afficher plusieurs éléments sous forme de grille respectivement verticale ou horizontale. Analysons les différents paramètres de leurs fonctions Composables, afin de voir comment concevoir ce type de grille. Pour cela, voici les signatures de ces fonctions :
@Composable
fun LazyVerticalGrid(
columns: GridCells,
modifier: Modifier = Modifier,
state: LazyGridState = rememberLazyGridState(),
contentPadding: PaddingValues = PaddingValues(0.dp),
reverseLayout: Boolean = false,
verticalArrangement: Arrangement.Vertical =
if (!reverseLayout)
Arrangement.Top
else
Arrangement.Bottom,
horizontalArrangement: Arrangement.Horizontal =
Arrangement.Start,
flingBehavior: FlingBehavior =
ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
content: LazyGridScope.() -> Unit
) {
/*...*/
}
@Composable
fun LazyHorizontalGrid(
rows: GridCells,
modifier: Modifier = Modifier,
state: LazyGridState = rememberLazyGridState(),
contentPadding: PaddingValues...
Contrôler et réagir à l’état de défilement
Le State Holder d’un lazy layout
Il est fréquent au sein d’une application de devoir réagir à l’état de défilement d’une liste ou de devoir actionner le défilement d’une liste vers une position donnée. Pour pouvoir effectuer cela, les lazy layouts disposent d’un State Holder. Il a la particularité d’être instancié par défaut dans les paramètres de ces layouts, comme nous pouvons le voir dans l’extrait de signature du lazy layout LazyColumn ci-après.
fun LazyColumn(
modifier: Modifier = Modifier,
state: LazyListState = rememberLazyListState(),
/*...*/
) {
/*...*/
}
LazyListState correspond ici au State Holder stockant et gérant les états de ce layout. Il s’instancie via la fonction rememberLazyListState() qui se charge en même temps de sauvegarder cet état à travers les recompositions et changements de configuration, à la manière de la fonction rememberSaveable{}. Pour utiliser ce State Holder dans notre code, il suffit donc de l’instancier nous-même et de le fournir en paramètre du lazy layout, comme illustré ici :
val listState = rememberLazyListState()
LazyColumn(
modifier = modifier,
state : LazyListState = listState
) {
/*...*/
}
Analysons maintenant plus en détail ce que LazyListState nous permet de faire.
Ce State Holder donne accès à différents états internes du layout, en particulier aux états suivants :
-
firstVisibleItemIndex, qui correspond...