Build a custom Staggered Flow Layout with Jetpack Compose

M S Lalith
6 min readJun 7, 2023

--

Introduction

Jetpack Compose is a modern toolkit for building native Android user interfaces using Kotlin. It provides a declarative approach to UI development, allowing developers to build UI components as functions. In this article, we will learn how to build below UI with Jetpack Compose. Let’s call it StaggeredFlowRow. Basically our UI looks similar to FlowRow except that the items in a row should stretch to fit horizontally.

To keep it simple, let’s take only

  • modifier — Allows applying additional modifications to the layout.
  • mainAxisSpacing — Represents the spacing between items along the main axis (horizontal axis).
  • crossAxisSpacing — Represents the spacing between rows along the cross axis (vertical axis).
  • content — Represents the content of the layout, specified as a lambda function.

The Layout function allows us to create a custom layout by defining how the child elements should be measured and positioned. This takes

  • content — The children composable to be laid out.
  • modifier — Modifiers to be applied to the layout.
  • measurePolicy — The policy defining the measurement and positioning of the layout. This can also be written as a lambda giving measurables & constraints.

Measuring and Arranging Child Elements

Within the lambda passed to the Layout function, the layout performs the measuring and arranging of child elements based on the provided constraints.

Measuring Child Elements

The measurables parameter is a list of child elements that need to be measured. In this case, each child element is a Measurable object.

First let’s calculates the intrinsic width of each child element using the maxIntrinsicWidth function. The height parameter is set to constraints.maxHeight to maintain the available height constraint for each child.

Now widths variable has the width information of each child.

Next, the total available width is obtained from the constraints.maxWidth property. This is the maximum allowed width that Layout can take.

Arranging Child Elements

We use an iterative approach to arrange the child elements within rows.

Our idea is to take first n items whose combined width sum is less than the totalWidth. Add these items to the sequence and remove them from remainingMeasurables. Repeat these steps until all the measurables are consumed.

We maintain currentWidth and keep on adding the width of children if the child fits in the sequence.

After determining the current row’s width, we calculate the remaining space available for distributing extra space evenly among the elements.

Here index will be having the number of items in a row.

Now we take measurable & width together using the zip function and measure each child in a row by calling measure function on the measurable object. It takes constraints with a minimum and maximum width set to newWidth, which includes the evenly distributed extra space.

The resulting placeables are a list of measured child elements with adjusted widths.

The placeables list, representing the elements in the current row, is added to the sequences list, which keeps track of all arranged element sequences.

Next we need to remove the children which are added to the sequence. Since index denotes the number of items in a row, we loop through index times and remove the first item in remainingMeasurables and remainingWidths lists.

Final Layout Calculation

Till now we found the position where every child should be placed at. Now we need to actually place them by calling layout function which takes a final width & height for this Composable.

The width we already know, totalWidth would be width of this Composable.

The height is what we need to calculate. If we think, we can have x rows with each row taking 1 or more items depending on their width. Now if we find the child having maximum height in each row and sum it up, this would become our totalHeight.

We loop through each sequence and place them at xPosition, yPosition. After placing each item in a sequence we increment xPosition by width of that placeable so that they will be place next to each other.

After placing all the items in a sequence, we need to move to next line. This is achieved by incrementing yPosition with the maximum height in this sequence.

This process is repeated until all the sequences are placed.

And this is how StaggeredFlowRow looks with above usage

Let’s improve by adding space between each sequence. To do this we take crossAxisSpacing as a parameter to StaggeredFlowRow.

First we convert crossAxisSpacing to px and also create crossAxisPositions list to maintain the y positions of each sequence.

After we build the sequence, we find the maximum height and add crossAxisSpacing to it. Remember to not add the spacing to the last sequence.

Now the totalHeight would be the sum of crossAxisPositions. After each sequence, we also need to increment yPosition by the crossAxisPositions at that index. With this change let’s see how our StaggeredFlowRow looks.

Let’s again improve by adding space between each item in a sequence. To do this we take mainAxisSpacing as a parameter to StaggeredFlowRow.

We convert mainAxisSpacing to px and also add it to currentWidth along with the width of the item being added to the sequence.

After the loop, we reduce the currentWidth by mainAxisSpacing because the spacing should be present between the items but not at the end.

This time we need to change the implementation a little bit. First we create an IntArray named sequenceMainAxisSizes having width of each item in the sequence and mainAxisPositions of same size as sequenceMainAxisSizes with 0 as default.

We make use of the Arrangement Compose already provides. Since we need our items to be spaced in between each other we use Arrangement.spacedBy(space = mainAxisSpacing) and call the arrange function which takes 3 parameters

  • totalSize — total width of the Composable
  • sizes — an IntArray of each item size
  • outPositions — an IntArray which will be updated with the positions after arranging.

Now our mainAxisPositions are filled with new positions after arrangement. So we take mainAxisPositions & sequence together using the zip function and place each child xPosition, yPosition. With this change let’s see how our StaggeredFlowRow looks.

Full Code:

Conclusion

In this article, we explored how to build a StaggeredFlowRow Composable, which is a custom layout for displaying items similar to FlowRow. We dissected the code step by step, understanding how it measures and arranges the child elements to achieve the desired layout. Jetpack Compose’s declarative approach simplifies the creation of custom layouts, and the Layout function provides the flexibility to define complex UI components tailored to specific requirements.

This has been implemented in my hobby project Focus Launcher. Feel free to checkout the repo.

--

--