ConstraintLayout Flow: Simple Grid Building Without Nested Layouts
ConstraintLayout chains are great, but they only work for one row of items. What if you have too many items to fit on one row? There hasn’t been a simple way to allow your chain to expand to multiple rows of items. With ConstraintLayout Flow, this changes.
ConstraintLayout Flow allows a long chain of items to wrap onto multiple rows or columns. This is similar to Google’s FlexboxLayout, which is an Android implementation of the idea of the flexible box layout from CSS. However, instead of using an actual ViewGroup to manage the contained items, ConstraintLayout Flow uses a virtual helper object, so your layout maintains its flat view hierarchy.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.constraintlayout.helper.widget.Flow android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:constraint_referenced_ids="item_1,item_2,item_3" /> <View android:id="@+id/item_1" android:layout_width="50dp" android:layout_height="50dp" /> <View android:id="@+id/item_2" android:layout_width="50dp" android:layout_height="50dp" /> <View android:id="@+id/item_3" android:layout_width="50dp" android:layout_height="50dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
Flow is used within a parent ConstraintLayout and is able to manage any View with a defined id. Notice the attribute
constraint_referenced_ids – this attribute defines the Views that will be managed by this constraint helper. If you’ve used
Barrier before, you’ll be familiar with this; it works exactly the same as it does in those virtual helper objects.
What can we use this for?
While there are many combinations of Flow attributes that result in a variety of layouts, I’ll focus on two use-cases that I think will be the most common usage of ConstraintLayout Flow: grid and flexbox-style layouts.
A pretty common ask of Android developers is to create a grid of items. Of course, we have GridLayout and RecyclerView with GridLayoutManager, but Flow is a good alternative for a couple of reasons:
- It keeps the layout flat, which is more efficient during layout calculation than nesting with GridLayout or RecyclerView.
- It’s simpler than setting up a RecyclerView and, for small lists of items, you won’t sacrifice the performance benefits of RecyclerView.
A grid setup is going to work best for items that are the same size, otherwise we’ll end up with a lot of empty space. To start, we’ll put 10 square views into a Flow like the one in the code above.
Right now, we haven’t specified any attributes for the Flow, so all the Flow attributes are at their defaults. The main attribute of Flow is
app:flow_wrapMode, which specifies how the Flow should handle elements that extend past the constraints. The default value of
none creates a regular ConstraintLayout chain, which extends past the edge of its constraints on both sides. To tell the Flow to wrap when it reaches the constraint, we’ll use
chain for wrap mode.
<androidx.constraintlayout.helper.widget.Flow android:layout_width="275dp" android:layout_height="200dp" android:background="#e4e4e4" app:constraint_referenced_ids="item_0,item_1,item_2,item_3,item_4,item_5,item_6,item_7,item_8,item_9" app:flow_wrapMode="chain" />
Now, instead of extending past the constraint edges on the left and right, the Flow makes the items wrap onto another line. However, since Flow treats each new line as a new chain, the last line doesn’t fit with the grid.
The other possibility for wrap mode is
aligned, which tells the Flow to line up the items vertically, as well as horizontally. In chain mode each row is treated as an independent chain, so items won’t necessarily line up vertically. In aligned mode, the 1st item of each chain will be in the 1st “column”, the 2nd item of each chain will be in the 2nd “column”, and so on. If we change our Flow to have
aligned wrap mode we get something closer to what we want.
We still have spaces between the views, though. Since each row is treated as a chain, we can borrow an idea from normal ConstraintLayout chains: the chain style. It works the same here, using
packed to determine how to distribute the remaining space in the chain. Since we want no spaces between each view, we’ll set
packed. Now we have a grid layout, made entirely with ConstraintLayout!
<androidx.constraintlayout.helper.widget.Flow android:layout_width="275dp" android:layout_height="200dp" android:background="#e4e4e4" app:constraint_referenced_ids="item_0,item_1,item_2,item_3,item_4,item_5,item_6,item_7,item_8,item_9" app:flow_wrapMode="aligned" app:flow_horizontalStyle="packed" />
A flexbox-style layout is similar to a grid, but the items don’t necessarily need to be the same size. Google actually already has a library that implements flexbox-style layout for Android, but again, the benefit here is that the Flow keeps the layout flat, so the layout computation is more efficient.
For example, if we changed our views to TextViews with dimensions of
wrap_content and gave each of them random words, we’ll end up with views that don’t fit neatly into a grid structure.
However, if we now change the Flow’s wrap mode back to
chain, the views will nest with each other more neatly.
What if we’d like those views to start at the left side of the container, instead of being centered? Similar to other ConstraintLayout elements, Flow has a bias attribute that controls which side of the constraint each row will be placed closer to. If we set the
app:flow_horizontalBias attribute to
0, the chains will hug the left side of the container.
If we don’t need the views to be nested so tightly with one another, we can also add space between the rows and columns using
app:flow_verticalGap. Notice that this only adds space between items, not at the front or end of each chain.
These two use cases definitely aren’t the only ways to use ConstraintLayout Flow, but they’re ones that immediately present themselves as real-world uses of the new API. Flow’s main draw is that it allows building more complex layouts without relying on nested ViewGroups which lessens the potential for your app to experience UI jank. I have also found that it is faster and easier to set up than an equivalent RecyclerView, so for small sets of items where you don’t need the recycling behavior, Flow could be a simpler alternative.
Given the combinations of layout attributes afforded to us by Flow and ConstraintLayout in general, there are many possible layouts that can be unexpected and unintuitive. Finding discrete use-case specific combinations of attributes will be the key to using this new ConstraintLayout API effectively.
As with all new APIs, experiment with different combinations of attributes to see what kind of layouts you can produce. I’m excited to see what other use-cases could be implemented using ConstraintLayout Flow!