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

Jedną z zasad Compose jest to, że elementy podrzędne należy mierzyć tylko raz. Dwukrotne zmierzenie elementów podrzędnych powoduje wyjątek czasu działania. Czasami jednak przed pomiarem dzieci potrzebujesz pewnych informacji na ich temat.

Funkcje wewnętrzne umożliwiają wysyłanie zapytań do elementów podrzędnych, 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ść, która pozwoli prawidłowo wyświetlać treści?
  • Modifier.height(IntrinsicSize.Max) – jaka jest maksymalna wysokość, która jest potrzebna do prawidłowego wyświetlania treści?

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

Funkcje pierwotne w akcji

Możesz utworzyć element kompozycyjny, który wyświetla na ekranie 2 teksty oddzielone separatorem:

2 elementy tekstowe obok siebie, z pionową linią podziału między nimi

Aby to zrobić, użyj elementu Row z 2 komponentami kompozycyjnymi Text, które wypełniają dostępne miejsce, i elementem Divider pośrodku. Divider powinien być tak wysoki jak najwyższy Text i powinien być 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
        )
    }
}

Divider rozszerza się na cały ekran, co nie jest pożądanym zachowaniem:

Dwa elementy tekstowe obok siebie, z rozdzielaczem między nimi, ale rozdzielacz rozciąga się poniżej dolnej krawędzi tekstu

Dzieje się tak, ponieważ Row mierzy każdy element podrzędny osobno, a wysokości Text nie można użyć do ograniczenia Divider.

Aby Divider wypełniał dostępną przestrzeń o określonej wysokości, użyj modyfikatora height(IntrinsicSize.Min).

height(IntrinsicSize.Min) dostosowuje rozmiar elementów podrzędnych, aby ich wysokość była równa minimalnej wysokości wewnętrznej. Ten modyfikator jest rekurencyjny, więc wysyła zapytanie do minIntrinsicHeight elementu Row i jego elementów podrzędnych.

Zastosowanie tego modyfikatora do kodu sprawi, że 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:

2 elementy tekstowe obok siebie z pionową linią podziału między nimi

Wysokość elementu Row jest określana w ten sposób:

  • Wartość minIntrinsicHeight elementu kompozycyjnego Row to maksymalna wartość minIntrinsicHeight jego elementów podrzędnych.
  • Wartość minIntrinsicHeight elementu Divider wynosi 0, ponieważ nie zajmuje on miejsca, jeśli nie podano ograniczeń.
  • Text minIntrinsicHeight to tekst dla konkretnego elementu width.
  • Dlatego ograniczenie height elementu Row staje się maksymalną wartością minIntrinsicHeight elementów Text.
  • Następnie Divider rozszerza height do ograniczenia height podanego przez Row.

Wymiary wewnętrzne w niestandardowych układach

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 elementu Layout, podczas tworzenia zastąp właściwości 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ą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.
}