Внутренние измерения в макетах Compose

Одно из правил Compose заключается в том, что дочерние элементы следует измерять только один раз; измерение дочерних элементов дважды приводит к исключению во время выполнения. Однако бывают случаи, когда вам необходима некоторая информация о дочерних элементах, прежде чем измерять их.

Функция Intrinsics позволяет запрашивать данные у дочерних элементов до того, как они будут фактически измерены.

К компонуемому объекту можно запросить его 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 , отображаемого в одну строку.

Внутренние механизмы в действии

Вы можете создать составной элемент, который отображает на экране два текста, разделенных разделителем:

Два текстовых элемента расположены рядом, разделенные вертикальной перегородкой.

Для этого используйте Row с двумя составными 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 определяется следующим образом:

  • minIntrinsicHeight объекта Row composable равен максимальному minIntrinsicHeight его дочерних элементов.
  • minIntrinsicHeight элемента Divider равно 0, поскольку он не занимает место, если не заданы никакие ограничения.
  • Параметр Text minIntrinsicHeight определяет минимальную высоту текста для заданной width .
  • Таким образом, ограничение height элемента Row становится равным максимальной minIntrinsicHeight элемента Text .
  • Затем Divider расширяет свою height до height заданного Row .

Внутренние параметры в ваших пользовательских макетах

При создании пользовательского Layout или модификатора layout автоматически рассчитываются внутренние размеры на основе приблизительных значений. Поэтому расчеты могут быть некорректными для всех макетов. Данные API предоставляют возможности для переопределения этих значений по умолчанию.

Чтобы задать внутренние параметры размеров вашего пользовательского Layout , переопределите значения minIntrinsicWidth , minIntrinsicHeight , maxIntrinsicWidth и maxIntrinsicHeight интерфейса 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.
        }
    )
}

При создании собственного модификатора layout переопределите соответствующие методы в интерфейсе 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.
}

{% verbatim %} {% endverbatim %} {% verbatim %} {% endverbatim %}