Systeminterne Messungen in Compose-Layouts

Eine der Regeln von Compose besteht darin, dass Sie Ihre untergeordneten Elemente nur einmal messen sollten. Bei zweimaligen Messungen von untergeordneten Elementen wird eine Laufzeitausnahme ausgelöst. Manchmal benötigen Sie jedoch Informationen über Ihre Kinder, bevor Sie sie messen.

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

Bei einer zusammensetzbaren Funktion können Sie nach intrinsicWidth oder intrinsicHeight fragen:

  • (min|max)IntrinsicWidth: Wie hoch ist angesichts dieser Höhe die minimale/maximale Breite, mit der du deine Inhalte richtig darstellen kannst?
  • (min|max)IntrinsicHeight: Wie hoch ist angesichts dieser Breite die minimale/maximale Höhe, mit der du deine Inhalte richtig darstellen kannst?

Wenn Sie beispielsweise die minIntrinsicHeight einer Text mit unendlichem width abfragen, wird der height des Text zurückgegeben, als ob der Text in einer einzelnen Zeile gezeichnet wäre.

Intrinsik in der Praxis

Stellen Sie sich vor, wir möchten eine zusammensetzbare Funktion erstellen, die zwei Texte auf dem Bildschirm anzeigt, die durch eine Trennlinie wie die folgende voneinander getrennt sind:

Zwei Textelemente nebeneinander mit vertikaler Trennlinie

Wie gehen wir vor? Wir können ein Row mit zwei Text-Elementen verwenden, das sich so weit wie möglich erweitert, und einem Divider in der Mitte. Die Divider soll so hoch wie die höchste Text 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
        )
        Divider(
            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 Divider den gesamten Bildschirm einnimmt, und das ist nicht das gewünschte Ergebnis:

Zwei Textelemente nebeneinander, zwischen denen sich eine Trennlinie befindet, die sich jedoch unter dem unteren Rand des Texts erstreckt.

Dies liegt daran, dass Row jedes untergeordnete Element einzeln misst und die Höhe von Text nicht zur Einschränkung von Divider verwendet werden kann. Wir möchten, dass Divider den verfügbaren Bereich mit einer bestimmten Höhe füllt. Dazu können wir den height(IntrinsicSize.Min)-Modifikator verwenden .

height(IntrinsicSize.Min) gibt die Größe der untergeordneten Elemente an, die so groß wie ihre minimale Größe sind. Da es rekursiv ist, werden Row und die untergeordneten minIntrinsicHeight abgefragt.

Wenn Sie dies auf unseren Code anwenden, funktioniert es 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
        )
        Divider(
            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 vertikaler Trennlinie

Der minIntrinsicHeight der zusammensetzbaren Funktion Row ist der maximale minIntrinsicHeight ihrer untergeordneten Elemente. Der minIntrinsicHeight des Divider-Elements ist 0, da er keinen Platz einnimmt, wenn keine Einschränkungen angegeben sind. Der Text minIntrinsicHeight entspricht dem Text mit einem bestimmten width. Daher ist die Einschränkung height des Row-Elements der maximale minIntrinsicHeight der Text-Werte. Divider erweitert dann seine height um die durch Row vorgegebene Einschränkung height.

Intrinsik in Ihren benutzerdefinierten Layouts

Beim Erstellen eines benutzerdefinierten Layout- oder layout-Modifikators werden intrinsische Messungen automatisch anhand von Näherung berechnet. Daher sind die Berechnungen möglicherweise nicht für alle Layouts korrekt. Diese APIs bieten Optionen zum Überschreiben dieser Standardeinstellungen.

Wenn Sie die intrinsischen Messungen des benutzerdefinierten Layout angeben möchten, überschreiben Sie beim Erstellen die minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth und maxIntrinsicHeight der MeasurePolicy-Oberfläche.

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