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 przypadku komponentu możesz poprosić o intrinsicWidth lub intrinsicHeight:

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

Jeśli np. 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.

Funkcje pierwotne w praktyce

Załóżmy, że chcemy utworzyć komponent wyświetlający na ekranie 2 teksty rozdzielone separatorem:

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

Jak możemy 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. Chcemy, aby Divider był tak wysoki jak najwyższy 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. W tym celu możemy 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 pionową linią rozdzielającą

Wartość Row kompozytowa będzie równa maksymalnej wartości minIntrinsicHeight jego elementów.minIntrinsicHeight Wartość minIntrinsicHeight elementu Divider to 0, ponieważ nie zajmuje on miejsca, jeśli nie podano żadnych ograniczeń. Wartość Text minIntrinsicHeight będzie taka jak wartość tekstu w danym 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ń. Dlatego obliczenia mogą być nieprawidł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ć wartości atrybutów wewnętrznych niestandardowego Layout, podczas jego tworzenia zastąpij atrybuty 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.
}