Pomiary wewnętrzne w układach tworzenia wiadomości

Jedną z reguł usługi Compose jest to, że elementy potomne należy mierzyć tylko raz. Podwójne zliczanie elementów potomnych powoduje wyjątek czasu wykonywania. Czasami jednak przed pomiarem potrzebujesz pewnych informacji o swoich dzieciach.

Za pomocą funkcji Intrinsics możesz wysyłać zapytania dotyczące elementów składowych, zanim zostaną one zmierzone.

W komponowalnym możesz poprosić o intrinsicWidth lub intrinsicHeight:

  • (min|max)IntrinsicWidth: Jaka jest minimalna/maksymalna szerokość, przy której treści są wyświetlane prawidłowo?
  • (min|max)IntrinsicHeight: Jaka jest minimalna/maksymalna wysokość, przy której można prawidłowo namalować treści?

Jeśli na przykład poprosisz o minIntrinsicHeight w przypadku Text z nieskończoną liczbą wierszy height, zwróci on height w przypadku Text tak, jakby tekst był zapisany w jednym wierszu.

Elementy wbudowane w praktykę

Załóżmy, że chcemy utworzyć funkcję kompozycyjną, w której na ekranie wyświetlają się 2 teksty rozdzielone separatorami w ten sposób:

Dwa elementy tekstowe umieszczone obok siebie z pionową linią rozdzielającą

Jak to zrobić? Możemy mieć Row z 2 Text wewnątrz, które rozszerza się tak bardzo, jak to możliwe, oraz Divider w środku. Divider musi mieć wysokość najwyższego elementu (Text) i cienki (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
        )
    }
}

Jeśli wyświetlimy podgląd, zobaczymy, że Divider zajmuje cały ekran, a nie chcemy tego:

Dwa elementy tekstowe obok siebie, rozdzielone linią, która sięga poniżej dolnego brzegu tekstu

Dzieje się tak, ponieważ funkcja Row mierzy każdy element podrzędny osobno, a wysokość elementu Text nie może być używana do ograniczania elementu Divider. Chcemy, aby element Divider wypełniał dostępną przestrzeń o określonej wysokości. Można do tego użyć modyfikatora height(IntrinsicSize.Min) .

height(IntrinsicSize.Min) określa rozmiary swoich elementów, które muszą mieć wysokość odpowiadającą ich minimalnej wysokości. Jest to zapytanie rekurencyjne, które przeszuka Row i jego podelementy minIntrinsicHeight.

Po zastosowaniu tego w naszym kodzie będzie on działać zgodnie z oczekiwaniami:

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

Z podglądem:

Dwa elementy tekstowe umieszczone obok siebie z pionowym separatorem

Wartość Row kompozytowa będzie równa maksymalnej wartości minIntrinsicHeight jego elementów.minIntrinsicHeight Wartość minIntrinsicHeight elementu Divider ma wartość 0, ponieważ nie zajmuje miejsca bez żadnych ograniczeń. Text minIntrinsicHeight to tekst tekstu o określonej wartości width. Dlatego ograniczenie height elementu Row będzie maksymalną wartością minIntrinsicHeight elementu Text. Divider rozszerzy wtedy swoje height na ograniczenie height podane przez Row.

Elementy niestandardowe w układach niestandardowych

Podczas tworzenia niestandardowego modyfikatora Layout lub layout pomiary wewnętrzne są obliczane automatycznie na podstawie przybliżeń. Z tego powodu obliczenia mogą nie być prawidłowe w przypadku niektórych układów. Te interfejsy API oferują opcje umożliwiające zastąpienie tych ustawień domyślnych.

Aby określić wewnętrzne pomiary niestandardowego Layout, podczas tworzenia go zastąp minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth i maxIntrinsicHeight interfejsu 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.
        }
    )
}

Podczas tworzenia niestandardowego modyfikatora layout zastąpij powiązane metody w interfejsie LayoutModifier.

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