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( )で行っています。
関連記事: