以下は↓のRecyclerViewについての文章を読みながらざっくり訳しました。
公式リファレンス:Android Developers > Docs > Reference > RecyclerView
RecyclerView とは
限られた画面の中に大きいデータセットを表示するための、フレキシブルに使えるView。
用語集
- Adapter: RecyclerView.Adapterのサブクラスで、データセットの中から表示すべきデータをViewに渡す。
- Position: Adapter内のアイテムの位置
- Index: 適用されている子Viewのインデックス。ViewGroup.getChildAt(int) で使用する。Positionとは対照的。
- Binding: Adapter内のPositionに対応するデータを子Viewに紐づけるプロセス。
- Recycle (view): 前にデータを表示していたviewはキャッシュに格納され、あとで同じタイプのデータを表示するのに再利用される。これによりViewのレイアウト展開や構築といった初期処理をスキップできるので、パフォーマンスを劇的に改善できる。
- Scrap (view): 一時的にレイアウトから外れた状態の子View。完全に親のRecyclerViewから外されることなく再利用されることもあるが、とくに再利用の必要が無ければ変更されることはなく、またviewが汚染された(=Dirty)とみなされる場合にはAdapterにより修正される。
- Dirty (view): 表示される前にAdapterによりバインドし直す必要のある子View。
RecicylerViewにおけるposition
RecyclerView.AdapterとRecyclerView.LayoutManagerがある程度abstractなので、layoutの計算中にデータセットの変更を検出することができ、それによりLayoutManagerはAdapterの変更を監視してアニメーションの再計算をしなくて済む。またviewのbindingが一度に起き、かつ不要なbindingがされないのでパフォーマンスを改善できる。
この理由により、RecyclerViewには2種のposition
に関連したメソッドがある。
- layout position: 現在のlayout内のアイテム位置。LayoutManagerにおけるposition。
- adapter position: Adapter内のアイテム位置。Adapterにおけるposition。
これらの2種のpositionは、adapter.notify~
イベントの伝達と更新されたレイアウトが再計算されるのにかかる時間を除けば同じである。
LayoutPosition
を返したり受け取ったりするメソッドは最新のレイアウトのpositionを使用する(例:RecyclerView.ViewHolder.getLayoutPosition()
、findViewHolderForLayoutPosition(int)
)。これらのpositionは最後のレイアウト計算までのすべての変更が反映されているので、ユーザーが今画面上で見ているものと矛盾しない。例えば画面上にアイテムの一覧が表示されていてユーザーが5番目の要素を要求する場合、このメソッドを使えばユーザーが見ているものと合致する。
もう一つのposition関連メソッドはAdapterPosition
形式となる(例:RecyclerView.ViewHolder.getAbsoluteAdapterPosition()
、RecyclerView.ViewHolder.getBindingAdapterPosition()
、findViewHolderForAdapterPosition(int)
)。最新のadapter positionがまだlayoutに反映されていない場合であっても必要な場合にこれらのメソッドを使う。例えば、ViewHolderのクリックでadapter内のアイテムにアクセスしたい場合、RecyclerView.ViewHolder.getBindingAdapterPosition()
を使うべきである。RececlerView.Adapter.notifyDataSetChanged()
が既に呼ばれているけれどまだ新しいレイアウトが計算されていない時には、これらのメソッドはadapter positionを計算することができないため、これらのメソッドが返すNO_POSITION
やnull
の取りあつかいに注意が必要となる。
RecyclerView.LayoutManager
を書くときは大抵layout positionを使用し、RecyclerView.Adapterを使用する場合はadapter positionを使用すると良い。
動的データの表示
RecyclerViewで更新できるデータを表示するために、Adapterは挿入、移動、削除といったシグナルを必要とする。これは手動でadapter.notify~
メソッドを呼んで作ることもできるし、RecyclerViewの提供するより簡単な機能を使用することもできる。
DiffUtilを使用してリストを比較する
RecyclerViewが(ネットワークやデータベースから)更新されるたびに最初からフェッチされるリストを表示している場合、[DiffUtil](https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil)
はリストのバージョン間の差分を計算できる。DiffUtil
は両方のリストを入力として差分を計算し、その結果はRecyclerViewに渡されUIパフォーマンスを保ち、意味の伝わる最小限のアニメーションと更新を引き起こす。このアプローチはそれぞれのリストが不変のコンテンツを持ってメモリ上に存在していて、更新を新しいインスタンスとして受け取る必要がある。また、UIレイヤーがソートを実装しておらず、ただデータを与えられた順番で表示している場合に限られる。
このアプローチの最大の利点は、どんな任意の変更も拡張できる点にある。アイテムの更新、移動、追加、削除などすべてが同じ方法で処理される。比較する間はリストの2つのコピーを持ち続けなければならず、かつそれらを変更することは避けなければならないが、異なるバージョンのリスト間で変更されていない要素を共有することができる。
これをRecyclerViewで行うには3つの基本的な方法がある。推奨はListAdapter
で、最小限のコードでリストをバックグラウンドスレッドで比較できるより高レベルのAPIだ。AsyncListDiffer
でもこの動作を行うことができるが、こちらはAdapterをサブクラスで定義する必要が無い。もっとコントロールしたい場合、DiffUtil
は差分を自分で計算するのに使用できる、より低いレベルのAPIとなる。どのアプローチを選択するかで、アイテムデータに基づいてどのように差分を計算するべきかが決まる。
ソートされたリストの変更
RecyclerViewが連続的に更新を受け取る場合(例えばアイテムXが挿入されたとか、アイテムYが削除されたなど)、リストの管理のためにSortedList
を使用することができる。アイテムの並べかたを定義しておけば、SortedListは自動的にRecyclerViewが使用する更新シグナルをトリガーする。SortedListは挿入・削除イベントを処理するだけで良い場合に適しており、メモリ上にリストのコピーを1つだけ保持するだけで済む。また、SortedList.replaceAdd(Object[])
で差分を計算することもできるが、このメソッドは上述のリスト差分を取得する動作よりも限定的である。
Pagingライブラリ
Pagingライブラリは差分に基づいたアプローチに加えてページごとの読み込みをサポートする。ライブラリはデータベースのようなデータソースやページ単位のネットワークAPIによって、自動で自身を更新するリストとして動作する[PagedList](https://developer.android.com/reference/androidx/paging/PagedList)
クラスを供給する。また常識にとらわれない、ListAdapter
やAsyncListDiffer
と似た、便利なリスト差分比較を利用できる。Pagingライブラリについてのより詳しい情報はライブラリドキュメントを参照。
以下はクラスの説明のテンプレートが続いています。
前置きの概説が長い文章だったのでざっくり訳しました。何かお気づきの点ありましたら教えていただけると大変ありがたいです。