Макеты Flow в Compose

FlowRow и FlowColumn – это функции, похожие на Row и Column, но отличающиеся тем, что элементы переносятся на следующую строку, когда в контейнере заканчивается место. Будет создано несколько строк или столбцов. Количество элементов в строке также можно контролировать, задав значения для параметров maxItemsInEachRow или maxItemsInEachColumn. Часто для создания адаптивных макетов можно использовать FlowRow и FlowColumn. Контент не будет обрезаться, если элементы слишком велики для одного из измерений. Кроме того, сочетание maxItemsInEach* и Modifier.weight(weight) позволяет создавать макеты, которые при необходимости заполняют или расширяют ширину строки или столбца.

Пример использования: чип или интерфейс фильтрации.

Пять чипов в строке FlowRow, показывающие перенос на следующую строку, когда больше нет места.
Рисунок 1. Пример для слова FlowRow

Как пользоваться отчетом

Чтобы использовать FlowRow или FlowColumn, создайте эти функции и поместите в них элементы, которые должны следовать стандартному потоку:

@Composable
private fun FlowRowSimpleUsageExample() {
    FlowRow(modifier = Modifier.padding(8.dp)) {
        ChipItem("Price: High to Low")
        ChipItem("Avg rating: 4+")
        ChipItem("Free breakfast")
        ChipItem("Free cancellation")
        ChipItem("£50 pn")
    }
}

Этот фрагмент кода приводит к появлению интерфейса, показанного выше, в котором элементы автоматически переносятся на следующую строку, когда в первой строке заканчивается место.

Особенности макета с перетеканием

В макетах с автоматическим размещением есть следующие функции и свойства, которые можно использовать для создания различных макетов в приложении.

Расположение по главной оси: горизонтальное или вертикальное расположение.

Главная ось – это ось, вдоль которой располагаются элементы (например, в FlowRow элементы размещаются по горизонтали). Параметр horizontalArrangement в FlowRow определяет, как свободное пространство распределяется между элементами.

В таблице ниже приведены примеры того, как задать значение параметра horizontalArrangement для объектов, относящихся к параметру FlowRow:

Горизонтальное расположение задано на FlowRow

Результат

Arrangement.Start (Default)

Объекты, упорядоченные по началу

Arrangement.SpaceBetween

Расположение объектов с промежутками между ними

Arrangement.Center

Объекты расположены в центре

Arrangement.End

Элементы, расположенные в конце

Arrangement.SpaceAround

Объекты, расположенные на расстоянии друг от друга

Arrangement.spacedBy(8.dp)

Элементы, расположенные на определенном расстоянии друг от друга (в dp)

Для FlowColumn доступны похожие варианты с verticalArrangement, по умолчанию используется Arrangement.Top.

Расположение по поперечной оси

Поперечная ось – это ось, перпендикулярная главной. Например, на изображении FlowRow это вертикальная ось. Чтобы изменить способ расположения содержимого контейнера на поперечной оси, используйте свойство verticalArrangement для FlowRow и horizontalArrangement для FlowColumn.

В таблице ниже приведены примеры того, как задавать разные значения параметра verticalArrangement для объектов с помощью параметра FlowRow.

Вертикальное расположение на устройстве FlowRow

Результат

Arrangement.Top (Default)

Раскладка со съемкой сверху

Arrangement.Bottom

Расположение контейнера внизу

Arrangement.Center

Центральное расположение контейнера

Для FlowColumn доступны похожие варианты с horizontalArrangement. По умолчанию используется значение Arrangement.Start.

Выравнивание отдельных объектов

Вы можете выровнять отдельные элементы в строке по-разному. Это отличается от verticalArrangement и horizontalArrangement, поскольку выравнивает элементы в пределах текущей строки. Вы можете применить его с помощью метода Modifier.align().

Например, если в контейнере FlowRow элементы имеют разную высоту, строка принимает высоту самого большого элемента и применяет Modifier.align(alignmentOption) к элементам:

Вертикальное выравнивание установлено на FlowRow

Результат

Alignment.Top (Default)

Объекты выровнены по верхнему краю

Alignment.Bottom

Объекты выровнены по нижнему краю

Alignment.CenterVertically

Объекты выровнены по центру

Для FlowColumn доступны похожие варианты. По умолчанию используется выравнивание Alignment.Start.

Максимальное количество элементов в строке или столбце

Параметры maxItemsInEachRow или maxItemsInEachColumn определяют максимальное количество элементов на главной оси, которое может быть в одной строке до переноса на следующую. По умолчанию установлено значение Int.MAX_INT, которое позволяет размещать как можно больше элементов, если их размеры позволяют им поместиться в строке.

Например, если задать значение maxItemsInEachRow, то в начальном макете будет только три элемента:

Максимальное значение не задано

maxItemsInEachRow = 3

В строке потока не задано максимальное значение Максимальное количество объектов в строке потока

Вес товаров

Вес увеличивает размер объекта в зависимости от его коэффициента и доступного места в строке, в которой он был размещен. Важно отметить, что между FlowRow и Row есть разница в том, как веса используются для расчета ширины элемента. Для Rows вес рассчитывается на основе всех товаров в Row. В случае с FlowRow вес определяется на основе элементов в строке, в которую помещен элемент, а не всех элементов в контейнере FlowRow.

Например, если у вас есть четыре объекта, расположенных на одной линии, и их веса составляют 1f, 2f, 1f, 3f, то общий вес будет равен 7f. Оставшееся в строке или столбце пространство будет разделено на 7f. Ширина каждого товара будет рассчитана по формуле: weight * (remainingSpace / totalWeight).

Вы можете использовать сочетание Modifier.weight и max items с FlowRow или FlowColumn, чтобы создать макет в виде сетки. Этот подход полезен для создания адаптивных макетов, которые подстраиваются под размер экрана устройства.

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

Сетка, созданная с помощью строки потока
Рисунок 2. Как создать сетку с помощью FlowRow

Чтобы создать сетку с одинаковыми размерами объектов, выполните следующие действия:

val rows = 3
val columns = 3
FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = rows
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .weight(1f)
        .clip(RoundedCornerShape(8.dp))
        .background(MaterialColors.Blue200)
    repeat(rows * columns) {
        Spacer(modifier = itemModifier)
    }
}

Важно отметить, что если вы добавите ещё один элемент и повторите его 10 раз вместо 9, то последний элемент займет весь последний столбец, поскольку общий вес для всей строки будет равен 1f:

Последний объект в сетке в полном размере
Рисунок 3. Как создать сетку с помощью FlowRow, чтобы последний элемент занимал всю ширину

Веса можно сочетать с другими функциями Modifiers, например Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) или Modifier.fillMaxWidth(fraction). Все эти модификаторы работают вместе, чтобы обеспечить адаптивный размер элементов в контейнере FlowRow (или FlowColumn).

Вы также можете создать чередующуюся сетку из объектов разных размеров, в которой два объекта занимают половину ширины, а один – всю ширину следующего столбца:

Чередование сетки с потоком строк
Рисунок 4. FlowRow с чередующимися размерами строк

Для этого используйте следующий код:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 2
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .clip(RoundedCornerShape(8.dp))
        .background(Color.Blue)
    repeat(6) { item ->
        // if the item is the third item, don't use weight modifier, but rather fillMaxWidth
        if ((item + 1) % 3 == 0) {
            Spacer(modifier = itemModifier.fillMaxWidth())
        } else {
            Spacer(modifier = itemModifier.weight(0.5f))
        }
    }
}

Дробные размеры

С помощью атрибута "размер" Modifier.fillMaxWidth(fraction) можно указать размер контейнера, в котором должен находиться товар. Это отличается от того, как работает параметр Modifier.fillMaxWidth(fraction) при применении к элементам Row или Column, поскольку элементы Row/Column занимают процент оставшейся ширины, а не всю ширину контейнера.

Например, следующий код дает разные результаты при использовании FlowRow и Row:

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 3
) {
    val itemModifier = Modifier
        .clip(RoundedCornerShape(8.dp))
    Box(
        modifier = itemModifier
            .height(200.dp)
            .width(60.dp)
            .background(Color.Red)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .fillMaxWidth(0.7f)
            .background(Color.Blue)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .weight(1f)
            .background(Color.Magenta)
    )
}

FlowRow: средний элемент с шириной, равной 0,7 ширины контейнера.

Дробная ширина с плавающей строкой

Row: средний элемент занимает 0,7 % оставшейся ширины Row.

Дробная ширина со строкой

fillMaxColumnWidth() и fillMaxRowHeight()

Применение Modifier.fillMaxColumnWidth() или Modifier.fillMaxRowHeight() к элементу внутри FlowColumn или FlowRow гарантирует, что элементы в том же столбце или строке будут иметь ту же ширину или высоту, что и самый большой элемент в столбце или строке.

Например, в этом примере с помощью FlowColumn показывается список десертов Android. Вы можете увидеть разницу в ширине каждого элемента, когда к элементам применяется Modifier.fillMaxColumnWidth(), и когда это не так, а элементы переносятся.

FlowColumn(
    Modifier
        .padding(20.dp)
        .fillMaxHeight()
        .fillMaxWidth(),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    maxItemsInEachColumn = 5,
) {
    repeat(listDesserts.size) {
        Box(
            Modifier
                .fillMaxColumnWidth()
                .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp))
                .padding(8.dp)
        ) {

            Text(
                text = listDesserts[it],
                fontSize = 18.sp,
                modifier = Modifier.padding(3.dp)
            )
        }
    }
}

Свойство Modifier.fillMaxColumnWidth() применено к каждому элементу

fillMaxColumnWidth

Ширина не меняется (элементы переносятся)

Не задана максимальная ширина столбца для заполнения