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 pozwoli prawidłowo wyświetlać 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 pozwoli prawidłowo wyświetlić treść?
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 wewnętrzne w praktyce
Możesz utworzyć element kompozycyjny, który wyświetla na ekranie 2 teksty oddzielone separatorem:
Aby to zrobić, użyj elementu Row z 2 komponentami 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 ) } }
Element Divider rozszerza się na cały ekran, co nie jest pożądanym zachowaniem:
Dzieje się tak, ponieważ Row mierzy każdy element podrzędny osobno, a wysokość Text nie może być używana do ograniczania Divider.
Aby element 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 były tak wysokie jak ich minimalna wysokość wewnętrzna. 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:
Wysokość elementu Row jest określana w ten sposób:
- Wartość
minIntrinsicHeightelementu kompozycyjnegoRowto maksymalna wartośćminIntrinsicHeightjego elementów podrzędnych. - Wartość
minIntrinsicHeightelementuDividerwynosi 0, ponieważ nie zajmuje on miejsca, jeśli nie podano ograniczeń. TextminIntrinsicHeightto tekst dla konkretnego elementuwidth.- Dlatego ograniczenie
heightelementuRowstaje się maksymalną wartościąminIntrinsicHeightelementówText. - Następnie
Dividerrozszerza swojeheightdo ograniczeniaheightpodanego przezRow.
Wartości 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 minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth i maxIntrinsicHeight interfejsu MeasurePolicy.
Zastąpienie musi być zgodne z tym, co robi measure. Na przykład w kolumnie niestandardowej, która układa elementy podrzędne pionowo i nadaje każdemu z nich pełną szerokość kolumny, minimalna wysokość wewnętrzna dla danego elementu width jest sumą minimalnych wysokości wewnętrznych elementów podrzędnych dla tego elementu width:
@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 { val childConstraints = constraints.copy(minHeight = 0) val placeables = measurables.map { measurable -> measurable.measure(childConstraints) } val width = constraints.constrainWidth( placeables.maxOfOrNull { it.width } ?: 0 ) val height = constraints.constrainHeight( placeables.sumOf { it.height } ) return layout(width, height) { var yPosition = 0 placeables.forEach { placeable -> placeable.placeRelative(x = 0, y = yPosition) yPosition += placeable.height } } } override fun IntrinsicMeasureScope.minIntrinsicHeight( measurables: List<IntrinsicMeasurable>, width: Int ): Int { return measurables.sumOf { measurable -> measurable.minIntrinsicHeight(width) } } // 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. Obliczenia wewnętrzne muszą być zgodne z measure. Na przykład w przypadku danego elementu height modyfikator, który dodaje stałe dopełnienie ze wszystkich stron, odejmuje dopełnienie pionowe przed wysłaniem zapytania do elementu podrzędnego minIntrinsicWidth i dodaje dopełnienie poziome do wyniku:
fun Modifier.myCustomModifier(padding: Dp = 8.dp) = this then object : LayoutModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val placeable = measurable.measure( constraints.offset(-horizontal, -vertical) ) val width = constraints.constrainWidth(placeable.width + horizontal) val height = constraints.constrainHeight(placeable.height + vertical) return layout(width, height) { placeable.place(paddingPx, paddingPx) } } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ): Int { val paddingPx = padding.roundToPx() val horizontal = paddingPx * 2 val vertical = paddingPx * 2 val childHeight = if (height == Constraints.Infinity) { Constraints.Infinity } else { (height - vertical).coerceAtLeast(0) } return measurable.minIntrinsicWidth(childHeight) + horizontal } // Other intrinsics related methods have a default value, // you can override only the methods that you need. }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Układy niestandardowe {:#custom-layouts}
- Linie wyrównania w Jetpack Compose
- Etapy Jetpack Compose