Systeminterne Messungen in Compose-Layouts

Eine der Regeln von Compose besagt, dass untergeordnete Elemente nur einmal gemessen werden sollten. Wenn untergeordnete Elemente zweimal gemessen werden, wird eine Laufzeit-Ausnahme ausgelöst. Manchmal benötigen Sie jedoch einige Informationen zu Ihren Kindern, bevor Sie sie messen können.

Mit intrinsischen Faktoren können Sie Kinder abfragen, bevor sie tatsächlich gemessen werden.

Sie können ein Composable nach seinem IntrinsicSize.Min oder IntrinsicSize.Max fragen:

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

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

Intrinsics in Aktion

Angenommen, wir möchten 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

Wie können wir das erreichen? Wir können ein Row mit zwei Texts haben, die sich so weit wie möglich ausdehnen, und ein Divider in der Mitte. Die Divider soll so hoch wie die höchste Text und dünn (width = 1.dp) sein.

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

Wenn wir uns das ansehen, sehen wir, dass Divider auf den gesamten Bildschirm ausgedehnt wird. Das ist nicht das, was wir möchten:

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. Wir möchten, dass Divider den verfügbaren Platz mit einer bestimmten Höhe ausfüllt. Dazu können wir den Modifikator height(IntrinsicSize.Min) verwenden .

height(IntrinsicSize.Min) legt die Höhe seiner untergeordneten Elemente auf die Mindesthöhe fest. Da sie rekursiv ist, werden Row und die 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 Vorschau:

Zwei Textelemente nebeneinander mit einer vertikalen Trennlinie dazwischen

Die minIntrinsicHeight der Row-Composable ist das Maximum der minIntrinsicHeight ihrer untergeordneten Elemente. Die minIntrinsicHeight des Divider-Elements ist 0, da es ohne Einschränkungen keinen Platz einnimmt. Die minIntrinsicHeight von Text ist die des Texts bei einem bestimmten width. Daher ist die height-Einschränkung des Row-Elements das Maximum minIntrinsicHeight der Text. Divider erweitert dann seine height auf die height-Einschränkung, die durch die Row vorgegeben wird.

Intrinsics in benutzerdefinierten Layouts

Wenn Sie einen benutzerdefinierten Layout- oder layout-Modifikator erstellen, werden die intrinsischen Messungen automatisch auf Grundlage von Schätzungen 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 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.
}