Modificateurs Compose

Les modificateurs vous permettent d'apporter des éléments décoratifs ou d'améliorer un composable. Ils permettent par exemple d'effectuer 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)

Les modificateurs sont des objets Kotlin standards. Pour créer un modificateur, appelez l'une des fonctions de la classe Modifier :

@Composable
private fun Greeting(name: String) {
    Column(modifier = Modifier.padding(24.dp)) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

Deux lignes de texte sur fond coloré, avec une marge intérieure autour du texte.

Vous pouvez associer ces fonctions pour les composer :

@Composable
private fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .padding(24.dp)
            .fillMaxWidth()
    ) {
        Text(text = "Hello,")
        Text(text = name)
    }
}

L'arrière-plan coloré derrière le texte prend désormais toute la largeur de l'appareil.

Dans le code ci-dessus, vous remarquerez que plusieurs fonctions sont utilisées.

  • padding ajoute de l'espace autour d'un élément.
  • fillMaxWidth fait en sorte que le composable remplisse la largeur maximale qui lui est attribuée par son parent.

Il est recommandé de faire en sorte que tous vos composables acceptent un paramètre modifier et transmettent ce modificateur à son premier enfant émettant un élément d'UI. Cela permet de réutiliser votre code plus facilement, et rend son comportement plus prévisible et intuitif. Pour en savoir plus, consultez les consignes relatives aux API de Compose, en particulier Éléments acceptant et suivant un paramètre modificateur.

L'ordre des modificateurs a de l'importance

L'ordre des fonctions de modificateur joue un rôle majeur. Étant donné que chaque fonction modifie le Modifier renvoyé par la fonction précédente, la séquence a une incidence sur le résultat final. Prenons un exemple :

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

La zone entière, y compris la marge intérieure autour des bords, répond aux clics.

Dans le code ci-dessus, toute la zone est cliquable, y compris la marge intérieure tout autour de l'élément, car le modificateur padding a été appliqué après le modificateur clickable. Si l'ordre des modificateurs est inversé, l'espace ajouté par padding ne réagit pas aux entrées utilisateur :

@Composable
fun ArtistCard(/*...*/) {
    val padding = 16.dp
    Column(
        Modifier
            .padding(padding)
            .clickable(onClick = onClick)
            .fillMaxWidth()
    ) {
        // rest of the implementation
    }
}

La marge intérieure autour de la mise en page ne répond plus aux clics.

Modificateurs intégrés

Jetpack Compose propose plusieurs modificateurs intégrés pour vous aider à améliorer un composable ou à lui ajouter des éléments décoratifs. Voici quelques modificateurs communément utilisés pour ajuster des mises en page.

padding et size

Par défaut, les mises en page fournies dans Compose encapsulent les enfants. Toutefois, vous pouvez définir une taille à l'aide du modificateur size:

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(/*...*/)
        Column { /*...*/ }
    }
}

Notez que la taille que vous avez spécifiée peut ne pas être respectée si elle ne répond pas aux contraintes provenant du parent de la mise en page. Si vous souhaitez que la taille du composable soit appliquée quelles que soient les contraintes entrantes, utilisez le modificateur requiredSize :

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.requiredSize(150.dp)
        )
        Column { /*...*/ }
    }
}

L'image enfant est plus grande que les contraintes de son parent.

Dans cet exemple, même avec la height du parent définie sur 100.dp, la hauteur de l'Image sera 150.dp, car le modificateur requiredSize prévaut.

Si vous souhaitez qu'une mise en page enfant remplisse toute la hauteur disponible autorisée par le parent, ajoutez le modificateur fillMaxHeight (Compose fournit également fillMaxSize et fillMaxWidth) :

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.size(width = 400.dp, height = 100.dp)
    ) {
        Image(
            /*...*/
            modifier = Modifier.fillMaxHeight()
        )
        Column { /*...*/ }
    }
}

La hauteur de l'image est égale à celle du parent.

Pour ajouter une marge intérieure tout autour d'un élément, définissez un modificateur padding.

Si vous souhaitez ajouter une marge intérieure au-dessus d'une ligne de base d'un texte afin d'atteindre une distance spécifique avec lehaut de la mise en page, utilisez le modificateur paddingFromBaseline :

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(
                text = artist.name,
                modifier = Modifier.paddingFromBaseline(top = 50.dp)
            )
            Text(artist.lastSeenOnline)
        }
    }
}

Texte avec une marge intérieure au-dessus.

Décalage

Pour décaler une mise en page de sa position d'origine, ajoutez le modificateur offset et définissez le décalage sur les axes x et y. Les décalages peuvent être positifs ou négatifs. La différence entre padding et offset est que l'ajout d'un offset à un composable ne modifie pas ses mesures :

@Composable
fun ArtistCard(artist: Artist) {
    Row(/*...*/) {
        Column {
            Text(artist.name)
            Text(
                text = artist.lastSeenOnline,
                modifier = Modifier.offset(x = 4.dp)
            )
        }
    }
}

Le texte a été décalé vers la droite de son conteneur parent.

Le modificateur offset est appliqué horizontalement en fonction de la direction de la mise en page. Dans un contexte d'affichage de gauche à droite, un offset positif déplace l'élément vers la droite, tandis que dans un contexte d'affichage droite à gauche, il déplace l'élément vers la gauche. Si vous devez définir un décalage sans tenir compte de la direction de la mise en page, consultez le modificateur absoluteOffset, pour lequel une valeur de décalage positive déplace toujours l'élément vers la droite.

Le modificateur offset fournit deux surcharges : offset, qui utilise les décalages comme paramètres, et offset, qui accepte un lambda. Pour en savoir plus sur l'utilisation de chacun de ces paramètres et sur la façon d'optimiser les performances, consultez la section Performances de Compose - Reporter les lectures le plus longtemps possible.

Sûreté des champs d'application dans Compose

Dans Compose, il existe des modificateurs qui ne sont utilisables que lorsqu'ils sont appliqués aux enfants de certains composables. Compose applique ces modificateurs par le biais de champs d'application personnalisés.

Par exemple, si vous souhaitez rendre un enfant aussi grand que le Box parent sans affecter la taille du Box, utilisez le modificateur matchParentSize. matchParentSize n'est disponible que dans BoxScope. Par conséquent, il ne peut être utilisé que sur un enfant dans un parent Box.

La sûreté des champs d'application vous évite d'ajouter des modificateurs qui ne fonctionneraient pas dans d'autres composables et champs d'application, et vous fait gagner du temps en cas d'erreur lors de tests.

Les modificateurs avec champ d'application transmettent au parent certaines informations qu'il doit connaître sur l'enfant. On les appelle également des modificateurs de données parents. Leur fonctionnement interne est différent des modificateurs à usage général, mais du point de vue de l'utilisation, cela n'a pas d'importance.

matchParentSize en Box

Comme indiqué ci-dessus, si vous souhaitez qu'une mise en page enfant ait la même taille qu'un conteneur Box parent sans affecter la taille ce celui-ci, utilisez le modificateur matchParentSize.

Notez que matchParentSize n'est disponible que dans le champ d'application Box, ce qui signifie qu'il ne s'applique qu'aux enfants directs de composables Box.

Dans l'exemple ci-dessous, l'élément enfant Spacer tire sa taille de son parent Box, qui tire lui-même ses dimensions des enfants de plus grande taille, ArtistCard dans ce cas.

@Composable
fun MatchParentSizeComposable() {
    Box {
        Spacer(
            Modifier
                .matchParentSize()
                .background(Color.LightGray)
        )
        ArtistCard()
    }
}

Conteneur rempli d'un arrière-plan gris.

Si fillMaxSize est utilisé à la place de matchParentSize, Spacer occupe alors tout l'espace disponible pour le parent, ce qui entraîne l'expansion du parent et le remplissage de l'espace disponible.

Arrière-plan gris remplissant l'écran.

weight dans les lieux suivants : Row et Column

Comme vous avez pu le constater dans la section précédente (Marge intérieure et taille), par défaut, la taille d'un composable est définie par le contenu qu'il encapsule. Vous pouvez définir la taille d'un composable pour qu'elle soit flexible dans les limites de son élément parent à l'aide du modificateur weight, qui n'est disponible que pour RowScope et ColumnScope.

Prenons un élément Row contenant deux composables Box. Le premier conteneur reçoit une valeur weight qui représente le double du second. Sa largeur est donc deux fois plus importante. Étant donné que la Row fait 210.dp de large, le premier Box fait 140.dp de large et le second 70.dp :

@Composable
fun ArtistCard(/*...*/) {
    Row(
        modifier = Modifier.fillMaxWidth()
    ) {
        Image(
            /*...*/
            modifier = Modifier.weight(2f)
        )
        Column(
            modifier = Modifier.weight(1f)
        ) {
            /*...*/
        }
    }
}

La largeur de l'image est deux fois plus importante que celle du texte.

Extraire et réutiliser des modificateurs

Plusieurs modificateurs peuvent être enchaînés pour décorer ou enrichir un composable. Cette chaîne est créée via l'interface Modifier qui représente une liste ordonnée et immuable de Modifier.Elements uniques.

Chaque Modifier.Element représente un comportement individuel, comme les comportements de mise en page, de dessin et de graphique, tous les comportements liés aux gestes, à la mise au point et à la sémantique, ainsi que les événements de saisie de l'appareil. Leur ordre est important : les éléments modificateurs ajoutés en premier seront appliqués en premier.

Parfois, il peut être utile de réutiliser les mêmes instances de chaîne de modificateur dans plusieurs composables, en les extrayant dans des variables et en les hissant dans des champs d'application plus élevés. Cela peut améliorer la lisibilité du code ou les performances de votre application pour plusieurs raisons :

  • La réallocation des modificateurs n'est pas répétée lors de la recomposition des composables qui les utilisent.
  • Les chaînes de modificateur peuvent être très longues et complexes. Réutiliser la même instance d'une chaîne peut donc réduire la charge de travail de l'environnement d'exécution Compose lorsqu'il doit les comparer.
  • Cette extraction favorise la propreté, la cohérence et la facilité de gestion du code dans le codebase.

Bonnes pratiques concernant la réutilisation des modificateurs

Créez vos propres chaînes Modifier et extrayez-les pour les réutiliser sur plusieurs composants composables. Il est parfaitement acceptable de simplement enregistrer des modificateurs, car il s'agit d'objets semblables à des données :

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

Extraire et réutiliser des modificateurs lors de l'observation d'états changeant fréquemment

Lorsque vous observez des états qui changent fréquemment dans des composables, comme des états d'animation ou scrollState, un grand nombre de recompositions peut être effectué. Dans ce cas, vos modificateurs sont alloués à chaque recomposition et potentiellement à chaque frame :

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // Creation and allocation of this modifier will happen on every frame of the animation!
        modifier = Modifier
            .padding(12.dp)
            .background(Color.Gray),
        animatedState = animatedState
    )
}

Au lieu de cela, vous pouvez créer, extraire et réutiliser la même instance du modificateur, et la transmettre au composable comme suit :

// Now, the allocation of the modifier happens here:
val reusableModifier = Modifier
    .padding(12.dp)
    .background(Color.Gray)

@Composable
fun LoadingWheelAnimation() {
    val animatedState = animateFloatAsState(/*...*/)

    LoadingWheel(
        // No allocation, as we're just reusing the same instance
        modifier = reusableModifier,
        animatedState = animatedState
    )
}

Extraire et réutiliser des modificateurs sans champ d'application

Les modificateurs peuvent être étendus ou limités à un composable spécifique. Dans le cas des modificateurs sans champ d'application, vous pouvez facilement les extraire en dehors de tout composable en tant que variables simples :

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

@Composable
fun AuthorField() {
    HeaderText(
        // ...
        modifier = reusableModifier
    )
    SubtitleText(
        // ...
        modifier = reusableModifier
    )
}

Cela peut s'avérer particulièrement utile en cas de combinaison avec des mises en page différées. Dans la plupart des cas, il est préférable que tous vos éléments (qui peuvent être nombreux) présentent exactement les mêmes modificateurs :

val reusableItemModifier = Modifier
    .padding(bottom = 12.dp)
    .size(216.dp)
    .clip(CircleShape)

@Composable
private fun AuthorList(authors: List<Author>) {
    LazyColumn {
        items(authors) {
            AsyncImage(
                // ...
                modifier = reusableItemModifier,
            )
        }
    }
}

Extraire et réutiliser des modificateurs avec champ d'application

Lorsque vous traitez des modificateurs limités à certains composables, vous pouvez les extraire au niveau le plus élevé possible et les réutiliser le cas échéant :

Column(/*...*/) {
    val reusableItemModifier = Modifier
        .padding(bottom = 12.dp)
        // Align Modifier.Element requires a ColumnScope
        .align(Alignment.CenterHorizontally)
        .weight(1f)
    Text1(
        modifier = reusableItemModifier,
        // ...
    )
    Text2(
        modifier = reusableItemModifier
        // ...
    )
    // ...
}

Vous ne devez transmettre les modificateurs avec champ d'application extraits qu'aux enfants directs du même champ. Pour en savoir plus sur l'importance de cette opération, consultez la section Sûreté des champs d'application dans Compose.

Column(modifier = Modifier.fillMaxWidth()) {
    // Weight modifier is scoped to the Column composable
    val reusableItemModifier = Modifier.weight(1f)

    // Weight will be properly assigned here since this Text is a direct child of Column
    Text1(
        modifier = reusableItemModifier
        // ...
    )

    Box {
        Text2(
            // Weight won't do anything here since the Text composable is not a direct child of Column
            modifier = reusableItemModifier
            // ...
        )
    }
}

Chaînage supplémentaire des modificateurs extraits

Vous pouvez enchaîner ou ajouter vos chaînes de modificateur extraites en appelant la fonction .then() :

val reusableModifier = Modifier
    .fillMaxWidth()
    .background(Color.Red)
    .padding(12.dp)

// Append to your reusableModifier
reusableModifier.clickable { /*...*/ }

// Append your reusableModifier
otherModifier.then(reusableModifier)

Gardez simplement à l'esprit que l'ordre des modificateurs est important.

En savoir plus

N'hésitez pas à consulter la liste complète des modificateurs, avec leurs paramètres et champs d'application.

Pour vous entraîner davantage à utiliser des modificateurs, vous pouvez également suivre l'atelier de programmation Mises en page de base dans Compose ou consulter le dépôt Now in Android.

Pour en savoir plus sur les modificateurs personnalisés et leur création, consultez la documentation Mises en page personnalisées - Utiliser le modificateur de mise en page.