RecyclerViewが空(アイテムが無い)の時、EmptyViewを表示する実装を行ったので紹介します。
EmptyViewとは
アイテムの変更が可能なRecyclerViewでアイテムを削除し続けると、最終的にRecyclerViewが空になります。この時、RecyclerViewの表示は空白になってしまいます。
空白は利用者に「バグ?」と誤解を与え兼ねないし、見た目もよくありません。
なので、RecyclerViewが空であることを利用者へ積極的に伝えるべきです。
EmptyViewは積極的に伝えるための手段です。下記はその例になります。
例のようにしておけば、RecyclerViewが空になっても状況がわかります。「利用者へ親切なアプリ」であると言えます。
EmptyViewの実現方法
EmptyViewは図にあるようにRecyclcerViewの上に重ねて配置します。
このEmptyViewのVisibilityを「RecyclerViewが空である・ない」の条件で切り替えます。「空である」であればVISIBLE(表示されている)、「空でない」であればGONE(取り外されている)といった感じです。
切り替えの処理はObserverの中で行うようにします。
Observerはアイテム変更の通知をトリガーに実行します。

EmptyViewの実装
EmptyViewを配置
EmptyViewはViewであれば何でもよいです。サンプルはTextViewにしました。
必ずRecyclerViewの上に表示されるように、レイアウトファイル(***.xml)はRecyclerViewの下へEmptyViewを記述します。
表示はレイアウトファイルの上部に記述されたViewから順番に積み上げられて行くからです。
また、RecyclerViewをEmptyViewで覆い隠すために、EmptyViewの境界(Top,Bottom,Left,Right)をRecyclerViewに合わせます。
サンプルのハイライト部分がその境界の指定です。
<androidx.constraintlayout.widget.ConstraintLayout
...
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcySample"
android:layout_width="200dp"
android:layout_height="280dp"
android:layout_marginTop="24dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/txtEmpty"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@android:color/holo_orange_light"
android:gravity="center"
android:text="リストは空です!"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/rcySample"
app:layout_constraintEnd_toEndOf="@+id/rcySample"
app:layout_constraintStart_toStartOf="@+id/rcySample"
app:layout_constraintTop_toTopOf="@+id/rcySample" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
サンプルのEmptyViewはオレンジ色のバックグラウンドに「リストは空です!」と表示しています。
アイテム変更の通知を取得
記事「RecyclerView:アイテムの変更」で、アダプターへ変更の通知(notifyメソッドの実行)を行うと、AdapterDataObserverのメソッドが実行される話を書きました。
この実行されるAdapterDataObserverに加えて、ユーザ定義のObserverを追加することが出来ます。追加するにはアダプターへObserverの登録が必要です。
Observerは複数登録できることに注意してください。登録の重複を防ぐ対策が必要になります。
サンプルはonStart( )で登録しonStop( )で解放して重複を防いでいます。
private val observer = object : RecyclerView.AdapterDataObserver() {
override fun onChanged() {
checkEmptyOfList() // EmptyViewのVisibilityを切り替え
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
checkEmptyOfList() // EmptyViewのVisibilityを切り替え
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
checkEmptyOfList() // EmptyViewのVisibilityを切り替え
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
checkEmptyOfList() // EmptyViewのVisibilityを切り替え
}
}
override fun onStart() {
super.onStart()
rcySample.adapter?.registerAdapterDataObserver(observer) // 登録
}
override fun onStop() {
super.onStop()
rcySample.adapter?.unregisterAdapterDataObserver(observer) // 解放
}
アイテム数が変化する可能性のあるメソッドで、EmptyViewのVisibilityを切り替える処理(checkEmptyOfList)を行っています。
notifyメソッドで実行されるAdapterDataObserverのメソッドは次の表を参考にしてください。
| notifyメソッド | 変更箇所 | observerメソッド | |
|---|---|---|---|
| 更新 | notifyDataSetChanged( ) | 全データ | onChanged |
| 変更 | notifyItemChanged(position: Int) | position | onItemRangeChanged |
| notifyItemRangeChanged( positionStart: Int, itemCount: Int) | positionStartから itemCount個 |
||
| 挿入 | notifyItemInserted(position: Int) | position | onItemRangeInserted |
| notifyItemRangeInserted( positionStart: Int, itemCount: Int) | positionStartから itemCount個 |
||
| 削除 | notifyItemRemoved(position: Int) | position | onItemRangeRemoved |
| notifyItemRangeRemoved( positionStart: Int, itemCount: Int) | positionStartから itemCount個 |
||
| 移動 from⇒to | notifyItemMoved( fromPosition: Int, toPosition: Int) | fromPosition toPosition | onItemRangeMoved |
| 変更 payload付 | notifyItemChanged( position: Int, payload: Any?) | position | onItemRangeChanged |
| notifyItemRangeChanged( positionStart: Int, itemCount: Int, payload: Any?) | positionStartから itemCount個 |
Visibilityを切り替え
アダプターからアイテム数(itemCount)が取得できます。
アイテム数を条件に「空である・ない」を判定してEmptyViewのVisibilityを切り替えます。
fun checkEmptyOfList() {
val _count = rcySample.adapter?.let { it.itemCount }?: 0
if(_count > 0)
txtEmpty.visibility = View.GONE // 空でない
else
txtEmpty.visibility = View.VISIBLE // 空である
}
override fun onResume() {
super.onResume()
checkEmptyOfList()
}
// ※rcySampel:RecyclerViewのインスタンス
// ※txtEmpty:EmptyView(例はTextView)のインスタンス
Activityの起動時にEmptyViewのVisibilityを切り替えたければ、Activityのライフサイクル中に行うのが良いでしょう。
サンプルはonResume( )で行っています。
関連記事:
