iTranslated by AI
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