iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦁

I didn't know about androidx.constraintlayout.helper.widget.Flow

に公開

When you want a layout that arranges an arbitrary number of Views and wraps them when the width is full, you might have customized GridView(GridLayout)#numColumns(auto_fit) or ChipGroup, or used FlexboxLayout. However, I discovered that a layout called Flow was added in ConstraintLayout 2.0 (I didn't know that).
Let's try it out right away.

When specifying in XML

Layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <androidx.constraintlayout.helper.widget.Flow
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:orientation="horizontal"
            app:flow_wrapMode="chain"
            app:constraint_referenced_ids="test1,test2,test3,test4,test5,test6"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
        <TextView
            android:id="@+id/test1"
            android:text="TEST"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <TextView
            android:id="@+id/test2"
            android:text="TEST2"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <TextView
            android:id="@+id/test3"
            android:text="TEST3"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <TextView
            android:id="@+id/test4"
            android:text="TESTあああああああああ"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <TextView
            android:id="@+id/test5"
            android:text="TEST5いいいいいいいいいいいい"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
        <TextView
            android:id="@+id/test6"
            android:text="TEST6ううううううううううう"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Place Flow under ConstraintLayout.
Specify the IDs of the layouts you want to display in constraint_referenced_ids.
In this case, I specified android:orientation="horizontal" because I wanted to arrange the Views horizontally.
Also, I specified app:flow_wrapMode="chain" because I wanted it to wrap when the width is insufficient.

Screen

Only the views test5 and test6, which couldn't fit on a single line, were wrapped. This is exactly what I intended.

When specifying dynamically

Overall layout

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraint"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <androidx.constraintlayout.helper.widget.Flow
            android:id="@+id/flow"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:orientation="horizontal"
            app:flow_wrapMode="chain"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Layout to be added dynamically

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</layout>

Code

class MainActivity : AppCompatActivity() {

    private val binding: MainActivityBinding by dataBinding()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        val test = (0..20).map { "test$it" }
        val ids = IntArray(test.size)

        test.forEachIndexed { index, text ->
            val binding = ViewTextBinding.inflate(LayoutInflater.from(this))
            val textView = binding.root as TextView
            textView.text = text
            textView.id = View.generateViewId()
            this.binding.constraint.addView(textView)
            ids[index] = textView.id
        }
        binding.flow.referencedIds = ids
    }
}

Adding dynamically is almost the same as specifying in XML: you just add the View you want to include to the ConstraintLayout. Then, simply pass the View IDs to the referencedIds of the Flow, and you're done.

Screen

It ended up looking like it was made by someone who doesn't know anything about margins, but they are properly arranged.

By the way, margins and such can be resolved by applying the following settings to the Flow in the XML:

app:flow_horizontalStyle="packed"
app:flow_horizontalGap="4dp"
app:flow_verticalGap="4dp"

The Main Point

At the end of the code that adds views dynamically,

ids.forEach {
    binding.constraint.removeView(binding.constraint.findViewById(it))
}

I added the above.

Wait? It's gone...
No, when I did this for a project at work, it wouldn't disappear and I literally cried!!
I really did cry!!
Believe me!!

How to handle when the View doesn't disappear

ViewGroup#removeView(index) worked as expected, so I used that instead.

Finally

It was a different kind of Flow than usual.
The end.

Discussion