Макеты потоков в Compose

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

Типичный пример — чип или фильтрующий пользовательский интерфейс:

5 фишек в 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 это вертикальная ось. horizontalArrangement изменить способ расположения всего содержимого внутри контейнера по поперечной оси, используйтеverterArrangement для FlowRow и verticalArrangement для FlowColumn .

Для FlowRow в следующей таблице показаны примеры установки различных verticalArrangement для элементов:

Вертикальное расположение установлено на 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 приводит к тому, что исходный макет будет содержать только 3 элемента:

Нет максимального набора

maxItemsInEachRow = 3

В строке потока не установлено максимальное значениеМаксимальное количество элементов, заданных в строке потока

Ленивая загрузка элементов потока

ContextualFlowRow и ContextualFlowColumn — это специализированные версии FlowRow и FlowColumn , которые позволяют отложенно загружать содержимое строки или столбца потока. Они также предоставляют информацию о положении элемента (индекс, номер строки и доступный размер), например, находится ли элемент в первой строке. Это полезно для больших наборов данных и если вам нужна контекстная информация об элементе.

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

Например, чтобы отобразить кнопку «+ (оставшееся количество элементов)» или «Показать меньше»:

val totalCount = 40
var maxLines by remember {
    mutableStateOf(2)
}

val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
    val remainingItems = totalCount - scope.shownItemCount
    ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
        if (remainingItems == 0) {
            maxLines = 2
        } else {
            maxLines += 5
        }
    })
}
ContextualFlowRow(
    modifier = Modifier
        .safeDrawingPadding()
        .fillMaxWidth(1f)
        .padding(16.dp)
        .wrapContentHeight(align = Alignment.Top)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.spacedBy(4.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    maxLines = maxLines,
    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
        minRowsToShowCollapse = 4,
        expandIndicator = moreOrCollapseIndicator,
        collapseIndicator = moreOrCollapseIndicator
    ),
    itemCount = totalCount
) { index ->
    ChipItem("Item $index")
}

Пример строк контекстного потока.
Рисунок 2. Пример ContextualFlowRow

Вес товара

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

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

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

Есть несколько различных примеров того, чего можно достичь, используя веса. Одним из примеров является сетка, в которой элементы имеют одинаковый размер, как показано ниже:

Сетка создана с помощью строки потока
Рисунок 3. Использование 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 :

Последний элемент в полном размере в сетке
Рисунок 4. Использование FlowRow для создания сетки, в которой последний элемент занимает всю ширину.

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

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

Чередующаяся сетка с потоковым рядом
Рисунок 5. 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 vs 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

Не задано изменение ширины (обертывание элементов)

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