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

Jedna z zasad Compose mówi, że elementy podrzędne należy mierzyć tylko raz. Dwukrotne mierzenie elementów podrzędnych powoduje wyjątek w czasie działania. Czasami jednak przed pomiarem elementów podrzędnych potrzebujesz pewnych informacji na ich temat.

Intrinsics umożliwia wysyłanie zapytań do elementów podrzędnych, zanim zostaną one zmierzone.

W przypadku elementu kompozycyjnego możesz poprosić o jego IntrinsicSize.Min lub IntrinsicSize.Max:

  • Modifier.width(IntrinsicSize.Min) – jaka jest minimalna szerokość potrzebna do prawidłowego wyświetlania treści?
  • Modifier.width(IntrinsicSize.Max) – jaka jest maksymalna szerokość potrzebna do prawidłowego wyświetlania 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ść potrzebna do prawidłowego wyświetlania treści?

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

Intrinsics w praktyce

Możesz utworzyć funkcję kompozycyjną, która wyświetla na ekranie 2 teksty oddzielone separatorem:

Dwa elementy tekstowe obok siebie, oddzielone pionową linią

Aby to zrobić, użyj elementu Row z 2 elementami kompozycyjnymi Text, które wypełniają dostępną przestrzeń, oraz elementu Divider pośrodku. Element Divider powinien być tak wysoki jak najwyższy element 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
        )
    }
}

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

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

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

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

height(IntrinsicSize.Min) zmienia rozmiar elementów podrzędnych tak, aby były tak wysokie jak ich minimalna wysokość intrinsics. Ponieważ ten modyfikator jest rekurencyjny, wysyła zapytanie o minIntrinsicHeight elementu Row i jego elementów podrzędnych.

Zastosowanie tego modyfikatora w kodzie sprawia, że działa on 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")
        }
    }
}

W podglądzie:

Dwa elementy tekstowe obok siebie, oddzielone pionową linią

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

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

Intrinsics w układach niestandardowych

Podczas tworzenia niestandardowego modyfikatora Layout lub layout pomiary intrinsics 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 wartości domyślnych.

Aby określić pomiary intrinsics niestandardowego elementu Layout, podczas jego tworzenia zastąp wartoś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.
}