Instruction
Understanding Flow Layouts
Compose offers two variants of the flow layout construct:
FlowRowFlowColumn
These composables are much like their Row and Column counterparts. They differ only in
how items are arranged.
With flow layouts, items “flow” into the next line when their container runs out of space. This creates a fluid UI with multiple rows and columns.
The idea is not novel; if you have dabbled in web development, it is quite similar to how Flexbox works in CSS.
Exploring FlowRow and FlowColumn
Before working with Flow Layouts, it’s worth exploring their inner workings. Here are the signatures of FlowRow and FlowColumn:
@Composable
@ExperimentalLayoutApi
inline fun FlowRow(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
maxItemsInEachRow: Int = Int.MAX_VALUE,
content: @Composable FlowRowScope.() -> Unit
) {
val measurePolicy = rowMeasurementHelper(
horizontalArrangement,
verticalArrangement,
maxItemsInEachRow
)
Layout(
content = { FlowRowScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
@Composable
@ExperimentalLayoutApi
inline fun FlowColumn(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
maxItemsInEachColumn: Int = Int.MAX_VALUE,
content: @Composable FlowColumnScope.() -> Unit
) {
val measurePolicy = columnMeasurementHelper(
verticalArrangement,
horizontalArrangement,
maxItemsInEachColumn
)
Layout(
content = { FlowColumnScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
FlowRow and FlowColumn have similar signatures and also resemble the Row and Column properties. They both accept:
-
content: Represents the flow layout’s content. -
modifier: Modifies the appearance and behavior. -
verticalArrangement: Controls the vertical arrangement of the layout’s children. -
horizontalArrangement: To control the horizontal arrangement of the layout’s virtual columns. -
maxItemsInEachRow/maxItemsInEachColumn: Controls the maximum number of items per row / column.
In addition to the signature, they both internally calculate a measurePolicy. The measure policy function defines how the layout should measure its children and itself. It’s a key part of the layout’s behavior, determining how it responds to incoming constraints from its parent and how it sizes and positions its children.
The measure policy is calculated based on the horizontal and vertical arrangements and the maximum items allowed per direction.
Using Flow Layouts
Flow layouts are excellent candidates for building UI where either the container items are of variable length or the UI must adapt to the amount of data coming in.
Note: Flow layouts are still an experimental API, meaning the implementation and features might change in the future. Keep this in mind when adopting flow layouts in your production applications.
The most common example of such a layout in mobile apps is a UI to represent tags on an item or for filtering items using chips.
The simplest usage of the flow layout is using FlowRow or FlowColumn to wrap the items that should follow the flow.
Take a look at the snippet below:
@Composable
private fun FilterUI() {
FlowRow(
modifier = Modifier.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)) {
FilterItem("Rating: High to Low")
FilterItem("Price: High to Low")
FilterItem("Rating: 4+")
FilterItem("Red")
}
}
The snippet results in the following UI:
You can see how the “Rating: 4+” filter item flows to the next line because there isn’t sufficient space in its initial row.
Using Item Weights in FlowRow
In cases where you want to control the width of individual items in a FlowRow, you can use weights to grow the item based on the supplied factor and the line’s available space.
Unlike Row, where the weight is based on all items in the row, FlowRow calculates weight based on items in the line that an item is placed in, not all items in the FlowRow.
Using weights, you can create pretty nifty UIs. The most common use case is creating a grid where items are equally sized.
@Composable
private fun GridUI() {
val numRows = 4
val numColumns = 4
FlowRow(
modifier = Modifier.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
maxItemsInEachRow = numRows
) {
val itemModifier = Modifier
.padding(4.dp)
.height(80.dp)
.weight(1f)
.clip(RoundedCornerShape(8.dp))
.background(Color.Green)
repeat(numRows * numColumns) {
Spacer(modifier = itemModifier)
}
}
The resulting UI would be the following.
You can also use weights to create an alternating grid of different item sizes, where one item takes up the entire width while the other two occupy half the width of the next column.
Here’s what the implementation would look like:
@Composable
private fun AlternatingGridUI() {
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.Red)
repeat(7) { item ->
if ((item + 1) % 3 == 0) {
Spacer(modifier = itemModifier.fillMaxWidth())
} else {
Spacer(modifier = itemModifier.weight(0.5f))
}
}
}}
And here’s what the resulting UI would look like:
Flow layouts are another example of how Jetpack compose makes building complex UIs like a masonry layout easy. Doing this with views would have been quite a challenge.