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 dont vous avez besoin 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 :
Pour ce faire, utilisez un Row avec deux composables Text qui remplissent l'espace disponible et un Divider au milieu. Le 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é :
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 plutôt le modificateur height(IntrinsicSize.Min).
height(IntrinsicSize.Min) dimensionne ses éléments enfants pour qu'ils soient aussi grands que leur hauteur intrinsèque minimale. Comme ce modificateur est récursif, il interroge la minIntrinsicHeight de Row et de ses éléments enfants.
Si l'on applique ce modificateur à notre code, on obtiendra le résultat attendu :
@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 :
La hauteur de Row est déterminée comme suit :
- La propriété
minIntrinsicHeightdu composableRowcorrespond à la valeurminIntrinsicHeightmaximale de ses éléments enfants. - La propriété
minIntrinsicHeightde l'élémentDividerest 0, car elle n'occupe pas d'espace si aucune contrainte n'est spécifiée. - La
TextminIntrinsicHeightest celle du texte d'unwidthspécifique. - Par conséquent, la contrainte
heightde l'élémentRowcorrespond à la valeurminIntrinsicHeightmaximale des élémentsText. Dividerva alors étendre saheighten fonction de la contrainteheightfournie parRow.
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.
Votre remplacement doit correspondre à ce que fait measure. Par exemple, dans une colonne personnalisée qui empile ses enfants verticalement et donne à chacun d'eux la largeur totale de la colonne, la hauteur intrinsèque minimale d'un width donné est la somme des hauteurs intrinsèques minimales de ses enfants pour ce width :
@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 { val childConstraints = constraints.copy(minHeight = 0) val placeables = measurables.map { measurable -> measurable.measure(childConstraints) } val width = constraints.constrainWidth( placeables.maxOfOrNull { it.width } ?: 0 ) val height = constraints.constrainHeight( placeables.sumOf { it.height } ) return layout(width, height) { var yPosition = 0 placeables.forEach { placeable -> placeable.placeRelative(x = 0, y = yPosition) yPosition += placeable.height } } } override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { return measurables.sumOf { measurable -> measurable.minIntrinsicHeight(width) } } // 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. Le calcul intrinsèque doit correspondre à measure. Par exemple, pour un height donné, un modificateur qui ajoute une marge intérieure fixe de tous les côtés soustrait la marge intérieure verticale avant d'interroger le minIntrinsicWidth de l'enfant, puis ajoute la marge intérieure horizontale au résultat :
fun Modifier.myCustomModifier(padding: Dp = 8.dp) = this then object : LayoutModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure( constraints.offset(-horizontal, -vertical) ) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ): Int { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val childHeight = if (height == Constraints.Infinity) { Constraints.Infinity } else { (height - vertical).coerceAtLeast(0) } return measurable.minIntrinsicWidth(childHeight) + horizontal } // Other intrinsics related methods have a default value, // you can override only the methods that you need. }
Recommandations personnalisées
- Remarque : Le texte du lien s'affiche lorsque JavaScript est désactivé
- Mises en page personnalisées {:#custom-layouts}
- Lignes d'alignement dans Jetpack Compose
- Phases de Jetpack Compose