🦁

androidx.constraintlayout.helper.widget.Flow知らんかった

2021/09/17に公開

不定の個数のViewを並べる&横幅いっぱいになったら改行するレイアウトが使いたい時に、
GridView(GridLayout)#numColumns(auto_fit)やChipGroupをカスタマイズしたりFlexboxLayoutを使っていたかと思うのですが、ConstraintLayout2.0からFlowというLayoutが追加されていました(知らんかった)
さっそく使ってみます。

xmlで指定する場合

レイアウト

<?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>

ConstraintLayoutの配下にFlowを置きます。
constraint_referenced_idsに表示したいレイアウトのidを指定します。
今回は横方向にViewを並べたかったのでandroid:orientation="horizontal" を指定
また、横幅が足りない場合は改行して欲しかったのでapp:flow_wrapMode="chain" を指定
しています。

画面

一行で収まらないtest5とtest6のViewだけが改行されました。
狙い通りです。

動的に指定する場合

画面全体のレイアウト

<?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>

同的に追加するレイアウト

<?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>

コード

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
    }
}

同的に追加する時もxmlで指定するのとほぼ同じで、ConstraintLayoutに追加したいViewをAddします。
そして、FlowのreferencedIdsにViewのidを渡してあげれば完了です。

画面


マージンとかを知らない人みたいにはなりましたが、並びました

ちなみにマージンなどはxmlのFlowに↓の設定をしてもらうことで解決します。

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

本題

動的にViewを追加するコードの最後に

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

を追加しました。

あれ?消えてる・・・
いや、業務でやった時に消えてくれなくて泣いたんですよ!!
本当に泣いたんですって!!
信じて!!

Viewが消えなかった時の対処方法

ViewGroup#removeView(index)は普通に動いてくれたので、そちらを使いました。

最後

いつもと違うFlowでした
おしまい

Discussion