Flow widget in ConstraintLayout

Mar 23, 2021

fingerlakes.jpg

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:

Screen Shot 2021-03-22 at 11.14.08 PM.png

And here is the same code rendering 10 tallies:

Screen Shot 2021-03-22 at 11.13.24 PM.png

And how about just a single tally?

Screen Shot 2021-03-22 at 11.34.21 PM.png

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 use MotionLayout animations.

  • For certain use cases, Flow can substitute a RecyclerView 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

 
Previous
Previous

ConstraintLayout Animation 101

Next
Next

Achieving Negative Margin in ConstraintLayout