Одно из правил Compose заключается в том, что дочерние элементы можно измерять только один раз. Если измерить их дважды, возникнет исключение времени выполнения. Однако бывают случаи, когда вам нужна информация о ваших детях, прежде чем измерять их.
Внутренние размеры позволяют запрашивать дочерние элементы до того, как они будут фактически измерены.
Для composable-функции можно запросить IntrinsicSize.Min или IntrinsicSize.Max:
Modifier.width(IntrinsicSize.Min)– минимальная ширина, необходимая для корректного показа контента.Modifier.width(IntrinsicSize.Max)– какая максимальная ширина нужна, чтобы контент отображался правильно?Modifier.height(IntrinsicSize.Min)– минимальная высота, необходимая для правильного отображения контента.Modifier.height(IntrinsicSize.Max)– максимальная высота, необходимая для корректного показа контента.
Например, если вы запросите minIntrinsicHeight Text с бесконечными ограничениями width в специальном макете, то получите height Text с текстом, нарисованным в одной строке.
Внутренние свойства в действии
Вы можете создать composable-функцию, которая отображает на экране два текста, разделенных разделителем:
Для этого используйте Row с двумя composable-функциями Text, которые заполняют доступное пространство, и Divider посередине. Высота Divider должна быть такой же, как у самого высокого элемента Text, а ширина – небольшой (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 разворачивается на весь экран, что нежелательно:
Это происходит потому, что Row измеряет каждый дочерний элемент по отдельности, а высоту Text нельзя использовать для ограничения Divider.
Чтобы Divider заполнял доступное пространство с заданной высотой, используйте модификатор height(IntrinsicSize.Min).
height(IntrinsicSize.Min) задает дочерним элементам высоту, равную их минимальной внутренней высоте. Поскольку этот модификатор является рекурсивным, он запрашивает minIntrinsicHeight для Row и его дочерних элементов.
Если применить этот модификатор к коду, он будет работать правильно:
@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") } } }
С предварительным просмотром:
Высота Row определяется следующим образом:
minIntrinsicHeightcomposable-функцииRow– это максимальное значениеminIntrinsicHeightее дочерних элементов.- Свойство
minIntrinsicHeightэлементаDividerравно 0, поскольку он не занимает места, если не заданы ограничения. TextminIntrinsicHeight– это цвет текста для определенногоwidth.- Поэтому ограничение
heightэлементаRowстановится максимальным значениемminIntrinsicHeightэлементовText. - Затем
Dividerрасширяет свойheightдо ограниченийheight, заданныхRow.
Внутренние размеры в пользовательских макетах
При создании пользовательского модификатора Layout или layout внутренние измерения рассчитываются автоматически на основе приближений. Поэтому расчеты могут быть неверными для всех макетов. Эти API предлагают варианты
переопределения этих значений по умолчанию.
Чтобы указать внутренние размеры пользовательского Layout, переопределите minIntrinsicWidth, minIntrinsicHeight, maxIntrinsicWidth и maxIntrinsicHeight интерфейса MeasurePolicy при его создании.
Переопределение должно соответствовать тому, что делает measure. Например, в специальном столбце, который размещает дочерние элементы вертикально и дает каждому дочернему элементу полную ширину столбца, минимальная внутренняя высота для заданного width является суммой минимальных внутренних высот дочерних элементов для этого 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. } ) }
При создании собственного модификатора layout переопределите связанные методы в интерфейсе LayoutModifier. Внутренний расчет должен соответствовать measure. Например, для заданного height модификатор, который добавляет фиксированный отступ со всех сторон, вычитает вертикальный отступ перед запросом minIntrinsicWidth дочернего элемента и добавляет горизонтальный отступ к результату:
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. }
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Собственные макеты {:#custom-layouts}
- Линии выравнивания в Jetpack Compose
- Этапы Jetpack Compose