Android RecyclerView で空の状態のViewを表示するAdapterDataObserverを作成

はじめに

今回は、RecyclerViewが空の状態に別のViewを表示する方法をご紹介します。
ListViewのEmptyViewみたいなものを想像していただければいいと思います。

ゴールとしては、RecyclerViewに setEmptyView(View) を呼び出すだけで設定できるまでを目指します。

Step1: レイアウトを作っていく

今回作成したのはシンプルに、「RecyclerView」と「RecyclerViewが空の場合に表示するView」の2つを用意しました。
ここでポイントになるのが、この2つは両方とも match_parent にしている部分です。VISIBLEの状態で正しく表示されないと正しく動作しないので、tools:visibilityなどをつかってしっかりとレイアウトを作成してください。

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:itemCount="6"
        tools:listitem="@layout/item_message"
        tools:visibility="gone" />

    <LinearLayout
        android:id="@+id/emptyView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:ignore="UseCompoundDrawables"
        tools:visibility="visible">

        <ImageView
            android:layout_width="114dp"
            android:layout_height="100dp"
            android:src="@drawable/ic_message_placeholder"
            android:tint="#aaaaaa"
            tools:ignore="ContentDescription" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="メッセージはありません"
            android:textColor="#aaaaaa"
            android:textSize="14sp" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

Step2: RecyclerView AdapterDataObserver をカスタム

RecyclerViewにはアイテムの変更を監視してくれている、「AdapterDataObserver」というものがあります。
今回はこれを使用して、アイテムが0個になった時を検知していきましょう。

実際に判定しているのは checkIfEmpty() ですが、「初期化時」「アイテム挿入時」「アイテム削除時」にそれぞれ判定をいれています。
「初期化時」・・・EmptyViewが設定された時
「アイテム挿入時」・・・RecyclerViewにアイテムが追加された時
「アイテム削除時」・・・RecyclerViewからアイテムが削除された時

/**
 * RecyclerViewの空状態を監視
 *
 * @property recyclerView RecyclerView
 * @property emptyView 空の場合に表示するView
 */
class RecyclerViewEmptyObserver(
    private val recyclerView: RecyclerView,
    private val emptyView: View
) : RecyclerView.AdapterDataObserver() {

    init {
        checkIfEmpty()
    }

    override fun onChanged() {
        checkIfEmpty()
    }

    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
        checkIfEmpty()
    }

    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
        checkIfEmpty()
    }

    /**
     * 空かどうかを判定してViewの表示状態を切り替える
     */
    private fun checkIfEmpty() {
        val adapter = recyclerView.adapter
        if (adapter != null) {
            val emptyViewVisible = adapter.itemCount == 0
            emptyView.visibleOrGone(emptyViewVisible)
            recyclerView.goneOrVisible(emptyViewVisible)
        }
    }
}

visibleOrGonegoneOrVisible についてはViewのExentionです。
こちらを参考にしていただければわかると思います。

qoopmk.hatenablog.jp

Step2: RecyclerView Extension を作成

RecyclerViewのExtensionに setEmptyView(view: View) を追加しておきましょう。
こうすることで、ActivityないしFragment側からこれを呼び出すだけで設定が完了します。
注意点としては、これを設定する前に RecyclerView の Adapter は先に設定しておきましょう。

/**
 * 空の状態のViewを設定
 *
 * @param view View
 */
fun RecyclerView.setEmptyView(view: View) {
    val observer = RecyclerViewEmptyObserver(this, view)
    adapter?.registerAdapterDataObserver(observer)
}

結果

以上これだけで、設定が完了です!!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    recyclerView.adapter = adapter
    recyclerView.setEmptyView(emptyView)
}

補足

通信などの非同期処理を実行して、アイテムを追加する場合はこの setEmptyView(view: View) のタイミングを取得完了時に設定してあげればOKです。