Systeminterne Messungen in Compose-Layouts

Eine der Regeln von Compose besagt, dass Sie Ihre untergeordneten Elemente nur einmal messen sollten. Wenn Sie sie zweimal messen, wird eine Laufzeitausnahme ausgelöst. Es gibt jedoch Situationen, in denen Sie vor dem Messen einige Informationen zu Ihren Kindern benötigen.

Mithilfe von „Intrinsics“ können Sie untergeordnete Elemente abfragen, bevor sie tatsächlich gemessen werden.

Sie können für ein Composeable die IntrinsicSize.Min oder IntrinsicSize.Max anfordern:

  • Modifier.width(IntrinsicSize.Min) – Welche Mindestbreite ist erforderlich, damit Ihre Inhalte richtig angezeigt werden?
  • Modifier.width(IntrinsicSize.Max) – Welche maximale Breite ist erforderlich, damit Ihre Inhalte richtig angezeigt werden?
  • Modifier.height(IntrinsicSize.Min) – Welche Mindesthöhe ist erforderlich, damit Ihre Inhalte richtig angezeigt werden?
  • Modifier.height(IntrinsicSize.Max) – Welche maximale Höhe ist erforderlich, damit Ihre Inhalte richtig angezeigt werden?

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

Intrinsische Funktionen in Aktion

Angenommen, wir möchten ein Composeable erstellen, in dem zwei Texte auf dem Bildschirm angezeigt werden, die durch eine Trennlinie getrennt sind, so wie hier:

Zwei Textelemente nebeneinander mit einer vertikalen Trennlinie dazwischen

Wie können wir das tun? Wir können einen Row mit zwei Texts in der Mitte haben, die sich so weit wie möglich ausdehnen, und einen Divider in der Mitte. Die Divider soll so hoch wie die höchste Text sein und dünn (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
        )
    }
}

In der Vorschau sehen wir, dass das Divider auf den gesamten Bildschirm maximiert wird. Das ist nicht das, was wir wollen:

Zwei Textelemente nebeneinander mit einer Trennlinie dazwischen, die jedoch bis unter den Text reicht

Das liegt daran, dass Row jedes Kind einzeln misst und die Höhe von Text nicht verwendet werden kann, um die Divider einzuschränken. Wir möchten, dass das Divider den verfügbaren Raum mit einer bestimmten Höhe füllt. Dazu können wir den Modifikator height(IntrinsicSize.Min) verwenden .

height(IntrinsicSize.Min) legt die Größe seiner untergeordneten Elemente fest, die so hoch sein müssen wie ihre minimale intrinsische Höhe. Da es sich um eine rekursive Abfrage handelt, werden Row und seine untergeordneten Elemente minIntrinsicHeight abgefragt.

Wenn wir das auf unseren 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 der Vorschau:

Zwei Textelemente nebeneinander mit einer vertikalen Trennlinie dazwischen

Die minIntrinsicHeight des Row-Elements ist die maximale minIntrinsicHeight seiner untergeordneten Elemente. Das minIntrinsicHeight des Divider-Elements ist 0, da es keinen Platz beansprucht, wenn keine Einschränkungen angegeben sind. Das minIntrinsicHeight des Text entspricht dem des Textes, der eine bestimmte width hat. Daher ist die height-Einschränkung des Row-Elements die maximale minIntrinsicHeight der Texts. Divider erweitert dann seinen height auf die height-Einschränkung, die durch Row vorgegeben ist.

Intrinsics in benutzerdefinierten Layouts

Wenn Sie einen benutzerdefinierten Layout- oder layout-Modifikator erstellen, werden die intrinsischen Messwerte automatisch anhand von Näherungen berechnet. Daher sind die Berechnungen möglicherweise nicht für alle Layouts korrekt. Diese APIs bieten Optionen, um diese Standardwerte zu überschreiben.

Wenn Sie die intrinsischen Messwerte Ihrer benutzerdefinierten Layout angeben möchten, überschreiben Sie beim Erstellen die minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth und maxIntrinsicHeight der Benutzeroberfläche MeasurePolicy.

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

Überschreiben Sie beim Erstellen des benutzerdefinierten layout-Modifikators 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.
}