Flow widget in ConstraintLayout
Mar 23, 2021
Let’s say you are building a layout that needs to draw a dynamic number of views within a container evenly spread out within a container.
Here is a practical example with 20 views (tallies) rendered:
And here is the same code rendering 10 tallies:
And how about just a single tally?
How do we build this?
One way to build this layout is by using a ConstraintLayout helper Flow widget.
The official documentation says:
Flow virtual layout. Added in 2.0 Allows positioning of referenced widgets horizontally or vertically, similar to a Chain.
Here is the layout of the container and the Flow child view to represent each tally:
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/tallyContainer" android:layout_width="match_parent" android:layout_height="48dp" android:paddingTop="8dp" android:paddingBottom="8dp"> <androidx.constraintlayout.helper.widget.Flow android:id="@+id/tally" android:layout_width="0dp" android:layout_height="match_parent" app:flow_horizontalStyle="spread" app:flow_wrapMode="chain" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
And this is how each tally is built:
<View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="6dp" android:layout_height="match_parent" android:background="@drawable/bg_rounded_corners" />
where the bg_rounded_corners is a drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?attr/colorPrimary" /> <corners android:radius="8dp" /> </shape>
And finally, here is the code used to render the tally container and its children:
private const val TALLY_COUNT = 20 class MainFragment : Fragment(R.layout.main_fragment) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val binding = MainFragmentBinding.bind(view) (1..TALLY_COUNT).forEach { _ -> val tallyView = LayoutInflater.from(context).inflate( R.layout.view_tally_item, binding.tallyContainer, false ).apply { id = View.generateViewId() } binding.tallyContainer.addView(tallyView) binding.tally.addView(tallyView) } } }
All this does is in a loop inflates a tally, assigns a dynamic view ID to it and adds it to the ConstraintLayout
container. The spacing between each tally is dynamic based on the number of tallies rendered. The resulting layout is optimized by being flat as well.
Flow widget benefits
The resulting layouts can be flat and therefore more optimized.
Lots of flexibility building various layouts using:
app:flow_wrapMode
app:flow_horizontalStyle
app:flow_horizontalGap
app:flow_horizontalAlign
app:flow_horizontalBias
and their app:flow_vertical* counterparts.
The styling can all be done in XML rather than programmatically when inflating and adding views.
Since this is implemented with
ConstraintLayout
, we get the ability to useMotionLayout
animations.For certain use cases,
Flow
can substitute aRecyclerView
which is more lightweight and easier to build.
Here is a repo that contains the source code for this example: https://github.com/jshvarts/ConstraintLayoutFlowDemo