Systeminterne Messungen in Compose-Layouts

Eine der Regeln von Compose ist, dass untergeordnete Elemente nur einmal gemessen werden sollten. Wenn untergeordnete Elemente zweimal gemessen werden, wird eine Laufzeitausnahme ausgelöst. Es gibt jedoch Fälle, in denen Sie einige Informationen zu untergeordneten Elementen benötigen, bevor Sie sie messen.

Mit Intrinsics können Sie untergeordnete Elemente abfragen, bevor sie tatsächlich gemessen werden.

Sie können für ein zusammensetzbares Element IntrinsicSize.Min oder IntrinsicSize.Max anfordern:

  • Modifier.width(IntrinsicSize.Min): Wie breit muss das Element mindestens sein, damit der Inhalt richtig angezeigt werden kann?
  • Modifier.width(IntrinsicSize.Max): Wie breit muss das Element höchstens sein, damit der Inhalt richtig angezeigt wird?
  • Modifier.height(IntrinsicSize.Min): Wie hoch muss das Element mindestens sein, damit der Inhalt richtig angezeigt wird?
  • Modifier.height(IntrinsicSize.Max) – Wie hoch muss das Element höchstens sein, damit der Inhalt richtig angezeigt wird?

Wenn Sie beispielsweise die minIntrinsicHeight eines Text-Elements mit unendlichen width-Einschränkungen in einem benutzerdefinierten Layout abfragen, wird die height des Text-Elements zurückgegeben, wobei der Text in einer einzigen Zeile dargestellt wird.

Intrinsics in der Praxis

Sie können eine zusammensetzbare Funktion erstellen, die zwei Texte auf dem Bildschirm anzeigt, die durch eine Trennlinie getrennt sind:

Zwei Textelemente nebeneinander mit einer vertikalen Trennlinie dazwischen

Verwenden Sie dazu eine Row mit zwei zusammensetzbaren Text-Elementen, die den verfügbaren Platz ausfüllen, und einer Divider in der Mitte. Die Divider sollte so hoch wie das höchste Text-Element und dünn sein (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
        )
    }
}

Die Divider wird auf den gesamten Bildschirm erweitert, was nicht das gewünschte Verhalten ist:

Zwei Textelemente nebeneinander mit einer Trennlinie dazwischen, die sich unter den Text erstreckt

Das liegt daran, dass Row jedes untergeordnete Element einzeln misst und die Höhe von Text nicht verwendet werden kann, um die Divider einzuschränken.

Wenn die Divider stattdessen den verfügbaren Platz mit einer bestimmten Höhe ausfüllen soll, verwenden Sie den Modifikator height(IntrinsicSize.Min).

height(IntrinsicSize.Min) legt die Höhe der untergeordneten Elemente auf die minimale intrinsische Höhe fest. Da dieser Modifikator rekursiv ist, wird die minIntrinsicHeight der Row und ihrer untergeordneten Elemente abgefragt.

Wenn Sie diesen Modifikator auf Ihren Code anwenden, funktioniert er wie erwartet:

@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")
        }
    }
}

Mit Vorschau:

Zwei Textelemente nebeneinander mit einer vertikalen Trennlinie dazwischen

Die Höhe der Row wird so bestimmt:

  • Die minIntrinsicHeight des zusammensetzbaren Row-Elements ist die maximale minIntrinsicHeight seiner untergeordneten Elemente.
  • Die minIntrinsicHeight des Divider-Elements ist 0, da es keinen Platz einnimmt, wenn keine Einschränkungen angegeben sind.
  • Die minIntrinsicHeight von Text entspricht der Höhe des Texts für eine bestimmte width.
  • Daher wird die height-Einschränkung des Row-Elements zur maximalen minIntrinsicHeight der Text-Elemente.
  • Die Divider erweitert dann ihre height auf die height-Einschränkung, die von der Row vorgegeben wird.

Intrinsics in benutzerdefinierten Layouts

Wenn Sie einen benutzerdefinierten Layout- oder layout-Modifikator erstellen, werden intrinsische Messungen automatisch anhand von Näherungswerten berechnet. Daher sind die Berechnungen möglicherweise nicht für alle Layouts korrekt. Diese APIs bieten Optionen zum Überschreiben dieser Standardwerte.

Wenn Sie die intrinsischen Messungen Ihres benutzerdefinierten Layout angeben möchten, überschreiben Sie beim Erstellen die Werte minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth, und maxIntrinsicHeight der MeasurePolicy-Schnittstelle.

@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.
        }
    )
}

Wenn Sie einen benutzerdefinierten layout-Modifikator erstellen, überschreiben Sie die zugehörigen Methoden in der LayoutModifier-Schnittstelle.

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