Rotating the phone to landscape caused users of the app to experience a suboptimal experience. Turns out we had some legacy code that was manually calculating the size and position of each “chip”. š±
The problem
How can we transform the following code into something that is simpler to reason on and maintain?
last == null -> { addView(textView, firstPillLayoutParams()) currentLineWidth = thisWidth below = textView currentDepth += topMarginFor(textView) + textViewHeight } currentLineWidth + thisWidth <= actualWidth -> { addView(textView, sameLineLayoutParams(last)) currentLineWidth += thisWidth }
Enter ConstraintLayout
with Flow
.
The solution
In ConstraintLayout 2.0, the Flow virtual layout was introduced. If you’re unfamiliar with Flow, this blog post provides a simple introduction. The main benefits of using Flow
are:
- it supports long chains of items that can wrap to multiple rows
- it allows you to keep your layout hierarchy flat
- elements can be positioned vertically or horizontally
Bingo. That’s exactly what I needed.
Creating the Flow instance
We’ll start by creating the Flow
instance and adding it to the ConstraintLayout
. We want it to have a unique id that we can use for layout purposes. Additionally, the flow should be the same size as the parent’s width and then the height should match its contents.
val flow = Flow(context).apply { id = generateViewId() layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) } addView(flow)
At this point, each element is displayed in a row. Some of the “chips” are clipped and there’s no spacing between them. Let’s add some styling to get us closer to the desired experience.
We’ll set the wrapMode
to Flow.WRAP_CHAIN
. That will allows the items to wrap to additional rows. We’ll also set a few more parameters to establish the alignment of the chips in the rows. This gets us event closer to what we want.
setWrapMode(Flow.WRAP_CHAIN) setHorizontalStyle(Flow.CHAIN_PACKED) setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START)
Next, we set the horizontalBias
to 0f
so that the “chips” will start towards the left for each row instead of in the center. Then we put in gaps for both the vertical and horizontal directions.
setHorizontalBias(0f)
setVerticalGap(pillMarginWidth)
setHorizontalGap(pillMarginWidth)
setOrientation(Flow.HORIZONTAL)
Now we have just we need. A flexible layout that’s easy to reason on and looks nice in both portrait and landscape modes. š
The complete function
Thanks for reading!