Пользовательские макеты

В Compose элементы пользовательского интерфейса представлены компонуемыми функциями, которые при вызове генерируют фрагмент пользовательского интерфейса, который затем добавляется в дерево пользовательского интерфейса, отображаемое на экране. Каждый элемент пользовательского интерфейса имеет одного родителя и потенциально много дочерних элементов. Каждый элемент также расположен внутри своего родителя, что указывается в виде позиции (x, y) и размера, заданного в виде width и height .

Родительские элементы определяют ограничения для своих дочерних элементов. Элементу предлагается указать свой размер в рамках этих ограничений. Ограничения определяют минимальную и максимальную width и height элемента. Если у элемента есть дочерние элементы, он может измерить каждый из них, чтобы определить свой размер. После того как элемент определит и сообщит свой собственный размер, у него появляется возможность определить, как размещать свои дочерние элементы относительно себя, как подробно описано в разделе «Создание пользовательских макетов» .

Размещение каждого узла в дереве пользовательского интерфейса — это трехэтапный процесс. Каждый узел должен:

  1. Измерьте любых детей.
  2. Определите его размер самостоятельно.
  3. Поместите своих детей
Три этапа компоновки узлов: измерение дочерних элементов, определение размера, размещение дочерних элементов.
Рисунок 1. Три этапа компоновки узлов: измерение дочерних элементов, определение размера и размещение дочерних элементов.

Использование областей видимости определяет , когда можно измерять и размещать дочерние элементы. Измерение компоновки может быть выполнено только во время проходов измерения и компоновки, а дочерний элемент может быть размещен только во время проходов компоновки (и только после того, как он был измерен). Благодаря областям видимости Compose, таким как MeasureScope и PlacementScope , это правило соблюдается на этапе компиляции.

Используйте модификатор макета.

Модификатор layout можно использовать для изменения способа измерения и размещения элемента. Layout — это лямбда-функция; её параметры включают измеряемый элемент, передаваемый как measurable , и входящие ограничения этого компонуемого объекта, передаваемые как constraints . Пользовательский модификатор layout может выглядеть следующим образом:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

Отобразите Text на экране и управляйте расстоянием от верхней до базовой линии первой строки текста. Именно это делает модификатор paddingFromBaseline ; здесь вы реализуете его в качестве примера. Для этого используйте модификатор layout , чтобы вручную разместить составной элемент на экране. Вот результат, когда верхний отступ Text установлен на 24.dp :

Показана разница между обычным отступом пользовательского интерфейса, который задает расстояние между элементами, и текстовым отступом, который задает расстояние от одной базовой линии до следующей.
Рисунок 2. Текст с примененным отступом paddingFromBaseline .

Вот код для создания этого отступа:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]

    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }
}

Вот что происходит в этом коде:

  1. В параметре лямбда-функции, measurable , вы измеряете Text представленный этим параметром, вызывая measurable.measure(constraints) .
  2. Размер компонуемого элемента задается вызовом метода layout(width, height) , который также возвращает лямбда-функцию, используемую для размещения обернутых элементов. В данном случае это высота между последней базовой линией и добавленным верхним отступом.
  3. Вы размещаете обёрнутые элементы на экране, вызывая метод placeable.place(x, y) . Если обёрнутые элементы не размещены, они не будут видны. Позиция по оси y соответствует верхнему отступу: положению первой базовой линии текста.

Чтобы убедиться, что всё работает как положено, используйте этот модификатор на Text :

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Несколько предварительных просмотров текстовых элементов; один показывает обычный отступ между элементами, другой — отступ от одной базовой линии до следующей.
Рисунок 3. Модификатор, примененный к составляемому Text и отображаемый в предварительном просмотре.

Создание пользовательских макетов

Модификатор layout изменяет только вызывающий компонуемый объект. Для измерения и компоновки нескольких компонуемых объектов используйте вместо него компонуемый объект Layout . Этот компонуемый объект позволяет измерять и компоновать дочерние элементы вручную. Все компоновки более высокого уровня, такие как Column и Row создаются с помощью компонуемого Layout .

В этом примере создается очень простая версия Column . Большинство пользовательских макетов следуют этому шаблону:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

Аналогично модификатору layout , measurables — это список дочерних элементов, которые необходимо измерить, а constraints — это ограничения от родительского элемента. Следуя той же логике, что и раньше, MyBasicColumn можно реализовать следующим образом:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Дочерние элементы, являющиеся составными, ограничены параметрами Layout (без ограничений minHeight ) и размещаются на основе yPosition предыдущего элемента.

Вот как будет использоваться этот пользовательский составной объект:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Несколько текстовых элементов расположены один над другим в столбце.
Рисунок 4. Реализация пользовательского Column .

Направление компоновки

Измените направление компоновки составного элемента, изменив локальную переменную композиции LocalLayoutDirection .

Если вы размещаете элементы компоновки вручную на экране, параметр LayoutDirection является частью LayoutScope модификатора layout или элемента компоновки Layout .

При использовании layoutDirection размещайте компонуемые элементы с помощью place . В отличие от метода placeRelative , place не меняется в зависимости от направления компоновки (слева направо или справа налево).

Настраиваемые макеты в действии

Узнайте больше о макетах и ​​модификаторах в разделе «Базовые макеты» в Compose , а также посмотрите, как работают пользовательские макеты, в примерах Compose, демонстрирующих их создание .

Узнать больше

Чтобы узнать больше о пользовательских макетах в Compose, обратитесь к следующим дополнительным ресурсам.

Видео

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