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

В 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, обратитесь к следующим дополнительным ресурсам.

Видео

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