Systeminterne Messungen in Compose-Layouts

Eine der Regeln von „Compose“ besagt, dass Sie Ihre untergeordneten Elemente nur einmal erfassen sollten. bei zweimaligen Messungen einer Laufzeitausnahme. Es gibt jedoch Situationen, in denen Sie vor dem Messen einige Informationen zu Ihren Kindern benötigen.

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

Sie können für ein Composeable die intrinsicWidth oder intrinsicHeight anfordern:

  • (min|max)IntrinsicWidth: Was ist aufgrund dieser Breite der minimale/maximale mit der Sie Ihre Inhalte richtig darstellen können?
  • (min|max)IntrinsicHeight: Was ist bei dieser Höhe die Mindest-/Höchsthöhe, bei der Sie Ihre Inhalte richtig malen können?

Wenn Sie beispielsweise nach der minIntrinsicHeight eines Text mit unendlicher height fragen, wird die height des Text zurückgegeben, als wäre der Text in einer einzigen Zeile gezeichnet.

Intrinsik 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
        )
        HorizontalDivider(
            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 wollen wir das nicht:

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 die Divider den verfügbaren Platz mit einer bestimmten Höhe. Dafür können wir den height(IntrinsicSize.Min)-Modifikator

height(IntrinsicSize.Min) legt fest, dass seine untergeordneten Elemente so groß sind wie ihre minimale intrinsische Höhe haben. Da sie rekursiv ist, fragt sie Row ab Kinder minIntrinsicHeight.

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
        )
        HorizontalDivider(
            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

Der Wert für minIntrinsicHeight des Row-Elements ist der maximale Wert für 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-Beschränkung des Row-Elements die maximale minIntrinsicHeight der Text. 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, sind intrinsische Messwerte werden anhand von Schätzungen automatisch berechnet. Dementsprechend wird der Berechnungen möglicherweise nicht für alle Layouts korrekt sind. Diese APIs bieten Optionen, um diese Standardwerte zu überschreiben.

So legst du die intrinsischen Messwerte deiner benutzerdefinierten Layout fest: minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth und maxIntrinsicHeight der MeasurePolicy wenn Sie sie 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.
        }
    )
}

Beim Erstellen des benutzerdefinierten layout-Modifikator die zugehörigen Methoden überschreiben in der LayoutModifier-Oberfläche.

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