Mesures intrinsèques dans les mises en page Compose

L'une des règles de Compose est de ne mesurer vos éléments enfants qu'une seule fois. Si vous les mesurez deux fois, une exception d'exécution est générée. Toutefois, il arrive que vous ayez besoin d'informations sur vos éléments enfants avant de les mesurer.

Les fonctionnalités intrinsèques vous permettent d'interroger des éléments enfants avant qu'ils ne soient réellement mesurés.

Dans le cas d'un composable, vous pouvez demander son IntrinsicSize.Min ou IntrinsicSize.Max :

  • Modifier.width(IntrinsicSize.Min) : quelle est la largeur minimale dont vous avez besoin pour afficher correctement votre contenu ?
  • Modifier.width(IntrinsicSize.Max) : quelle est la largeur maximale dont vous avez besoin pour afficher correctement votre contenu ?
  • Modifier.height(IntrinsicSize.Min) : quelle est la hauteur minimale requise pour afficher correctement votre contenu ?
  • Modifier.height(IntrinsicSize.Max) : quelle est la hauteur maximale dont vous avez besoin pour afficher correctement votre contenu ?

Par exemple, si vous demandez la minIntrinsicHeight d'un Text avec des contraintes width infinies dans une mise en page personnalisée, la height du Text est renvoyée avec le texte dessiné sur une seule ligne.

Fonctionnalités intrinsèques en action

Vous pouvez créer un composable qui affiche à l'écran deux éléments textuels séparés par un séparateur :

Deux éléments de texte côte à côte, séparés par une ligne verticale

Pour ce faire, utilisez un Row avec deux composables Text qui remplissent l'espace disponible et un Divider au milieu. Divider doit être aussi grand que l'élément Text le plus grand et doit être fin (width = 1.dp).

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

Le Divider s'étend sur tout l'écran, ce qui n'est pas le comportement souhaité :

Deux éléments de texte côte à côte, séparés par un séparateur, mais le séparateur s'étend sous le bas du texte

Cela est dû au fait que Row mesure chaque élément enfant séparément et que la hauteur de Text ne peut pas être utilisée pour limiter le Divider.

Pour que Divider remplisse l'espace disponible avec une hauteur donnée, utilisez le modificateur height(IntrinsicSize.Min).

height(IntrinsicSize.Min) dimensionne ses éléments enfants en les forçant à être aussi grands que leur hauteur intrinsèque minimale. Comme ce modificateur est récursif, il interroge le minIntrinsicHeight du Row et de ses enfants.

Si vous appliquez ce modificateur à votre code, il fonctionnera comme prévu :

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        VerticalDivider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

// @Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

Avec l'aperçu :

Deux éléments de texte côte à côte, séparés par une ligne verticale

La hauteur de Row est déterminée comme suit :

  • La propriété minIntrinsicHeight du composable Row correspond à la valeur minIntrinsicHeight maximale de ses éléments enfants.
  • La propriété minIntrinsicHeight de l'élément Divider est 0, car elle n'occupe pas d'espace si aucune contrainte n'est spécifiée.
  • Le minIntrinsicHeight Text est celui du texte pour un width spécifique.
  • Par conséquent, la contrainte height de l'élément Row devient la valeur minIntrinsicHeight maximale des éléments Text.
  • Le Divider étend ensuite son height à la contrainte height fournie par Row.

Fonctionnalités intrinsèques dans vos mises en page personnalisées

Lorsque vous créez un modificateur Layout ou layout personnalisé, les mesures intrinsèques sont calculées automatiquement en fonction d'approximations. Par conséquent, les calculs ne seront peut-être pas corrects pour toutes les mises en page. Ces API offrent des options pour remplacer ces valeurs par défaut.

Pour spécifier les mesures intrinsèques de votre Layout personnalisée, remplacez les valeurs minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth et maxIntrinsicHeight de l'interface MeasurePolicy lors de sa création.

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier,
        measurePolicy = object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

Lorsque vous créez votre modificateur layout personnalisé, remplacez les méthodes associées dans l'interface LayoutModifier.

fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
        // ...
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int {
        // Logic here
        // ...
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
}