Systeminterne Messungen in Compose-Layouts

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

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

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

  • (min|max)IntrinsicWidth: Was ist angesichts dieser Breite die minimale/maximale Breite, mit der Sie Ihre Inhalte richtig darstellen können?
  • (min|max)IntrinsicHeight: Was ist angesichts dieser Höhe die minimale/maximale Höhe, die Sie Ihre Inhalte angemessen darstellen können?

Wenn Sie beispielsweise das minIntrinsicHeight einer Text mit unendlichem height fragen, wird das height des Text zurückgegeben, als ob der Text in einer einzelnen Zeile gezeichnet wurde.

Intrinsik in Aktion

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

Zwei Textelemente nebeneinander, zwischen ihnen eine vertikale Trennlinie

Wie können wir das tun? Wir können eine Row mit zwei Text-Elementen haben, die so weit wie möglich erweitert werden, und einer Divider in der Mitte. Divider soll so hoch wie das 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
        )
        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 auf den gesamten Bildschirm maximiert wird. Das ist nicht das, was wir möchten:

Zwei Textelemente nebeneinander, mit einer Trennlinie dazwischen, die sich jedoch unterhalb des Textes nach unten erstreckt

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

Bei height(IntrinsicSize.Min) wird festgelegt, dass seine untergeordneten Elemente so hoch sein müssen wie ihre Mindesthöhe. Da sie rekursiv ist, werden Row und die untergeordneten minIntrinsicHeight abgefragt.

Wenn wir 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 Vorschau:

Zwei Textelemente nebeneinander, zwischen ihnen eine vertikale Trennlinie

minIntrinsicHeight der zusammensetzbaren Funktion der Row ist das Maximum von minIntrinsicHeight der untergeordneten Elemente. Das minIntrinsicHeight des Divider-Elements ist 0, da es keinen Platz belegt, wenn keine Einschränkungen angegeben sind. Das Text-minIntrinsicHeight ist der Text des Texts für eine bestimmte width. Daher ist die Einschränkung height des Row-Elements die maximale minIntrinsicHeight der Text. Divider erweitert dann sein height auf die durch Row vorgegebene Einschränkung height.

Intrinsische Elemente in Ihren benutzerdefinierten Layouts

Wenn Sie einen benutzerdefinierten Layout- oder layout-Modifikator erstellen, werden intrinsische Messwerte automatisch auf der Grundlage von Näherungswerte 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 Messwerte Ihrer benutzerdefinierten Layout angeben möchten, überschreiben Sie minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth und maxIntrinsicHeight der MeasurePolicy-Oberfläche beim Erstellen.

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