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

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

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

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

  1. Измерьте всех детей
  2. Определите свой собственный размер
  3. Поместите его детей

Три шага компоновки узла: измерение дочерних элементов, выбор размера, размещение дочерних элементов

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

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

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

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

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

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

Вот код для создания такого интервала:

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))
    }
}

Несколько предварительных просмотров текстовых элементов: один показывает обычные отступы между элементами, другой показывает отступы от одной базовой линии до другой

Создавайте индивидуальные макеты

Модификатор 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!")
    }
}

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

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

Измените направление компоновки компонуемого объекта, изменив локальный объект композиции LocalLayoutDirection .

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

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

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

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

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

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

Видео

{% дословно %} {% endverbatim %} {% дословно %} {% endverbatim %}