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

Jedna z zasad Compose mówi, że dzieci należy mierzyć tylko raz. Dwukrotne zmierzenie dzieci powoduje wyjątek w czasie działania. Czasami jednak przed pomiarem musisz uzyskać pewne informacje o dzieciach.

Intrinsics umożliwia wysyłanie zapytań dotyczących dzieci, zanim zostaną one zmierzone.

W przypadku funkcji kompozycyjnej możesz poprosić o jej IntrinsicSize.Min lub IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) – jaka jest minimalna szerokość, która jest potrzebna do prawidłowego wyświetlania treści?
  • Modifier.width(IntrinsicSize.Max) – Jaka jest maksymalna szerokość, której potrzebujesz, aby prawidłowo wyświetlać treści?
  • Modifier.height(IntrinsicSize.Min) – Jaka jest minimalna wysokość potrzebna do prawidłowego wyświetlania treści?
  • Modifier.height(IntrinsicSize.Max) – Jaka jest maksymalna wysokość, której potrzebujesz, aby prawidłowo wyświetlać treści?

Jeśli na przykład zapytasz o minIntrinsicHeight Text z nieskończonymiwidth ograniczeniami w układzie niestandardowym, zwróci height Text z tekstem narysowanym w jednym wierszu.

Funkcje pierwotne w praktyce

Załóżmy, że chcemy utworzyć komponent, który wyświetla na ekranie 2 teksty oddzielone separatorem, tak jak poniżej:

Dwa elementy tekstowe obok siebie, oddzielone pionową linią

Jak możemy to zrobić? Możemy mieć Row z 2 Text w środku, który rozszerza się tak bardzo, jak to możliwe, oraz Divider poś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
        )
        VerticalDivider(
            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 rozszerza się na cały ekran, a tego nie chcemy:

Dwa elementy tekstowe obok siebie, oddzielone separatorem, który sięga poniżej dolnej krawędzi tekstu.

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

height(IntrinsicSize.Min) wymusza na elementach podrzędnych wysokość równą ich minimalnej wysokości wewnętrznej. Ponieważ jest to zapytanie rekurencyjne, wyszuka ono Row i jego elementy podrzędne minIntrinsicHeight.

Jeśli zastosujemy to 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
        )
        VerticalDivider(
            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 obok siebie, oddzielone pionową linią

Wartość Row elementu kompozycyjnego minIntrinsicHeight będzie maksymalną wartością minIntrinsicHeight jego elementów podrzędnych. Wartość Divider elementu minIntrinsicHeight wynosi 0, ponieważ nie zajmuje on miejsca, jeśli nie podano żadnych ograniczeń. Wartość Text minIntrinsicHeight będzie wartością tekstu przy danym width. Dlatego ograniczenie height elementu Row będzie maksymalną wartością minIntrinsicHeight z wartości Text. Divider rozszerzy wtedy swój height do height ograniczenia podanego przez Row.

Wartości wewnętrzne w układach niestandardowych

Podczas tworzenia niestandardowego modyfikatora Layout lub layout pomiary wewnętrzne są obliczane automatycznie na podstawie przybliżeń. Dlatego obliczenia mogą nie być prawidłowe w przypadku wszystkich układów. Te interfejsy API oferują opcje zastępowania tych ustawień domyślnych.

Aby określić pomiary wewnętrzne niestandardowego Layout, zastąp wartości minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidthmaxIntrinsicHeight interfejsu MeasurePolicy podczas jego tworzenia.

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