はじめに
RecyclerViewでnotify()系のメソッドでアイテムを更新することは多い思います。
最も手軽な方法はnotifyDataSetChanged()でアイテム全体を更新することですが、アイテムひとつを更新したい時などでは無駄にViewが再生成されパフォーマンスが劣ります。
更新前後のリストを比較して、必要最小限のアイテムを更新することが望ましい方法です。
DiffUtilとは
DiffUtilは2つのListを比較して差分を計算するユーティリティクラスです。
これをRecyclerViewで使うことで、変更があった際に差分だけ更新され、更新された部分にアニメーションがかかるようになります。
このDiffUtilは、Listのサイズが大きい場合や複雑な場合には、計算に時間がかかるためバックグラウンドスレッドが推奨されています。
そしてこの非同期処理を担ってくれるのがAsyncListDifferです。AsyncListDifferを使えば、KotlinCoroutineなどで非同期処理を自分で書くことなく差分を計算できます。
RecyclerView.Adapterでの使用例
class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
private final AsyncListDiffer<User> mDiffer = new AsyncListDiffer(this, DIFF_CALLBACK);
@Override
public int getItemCount() {
return mDiffer.getCurrentList().size();
}
public void submitList(List<User> list) {
mDiffer.submitList(list);
}
@Override
public void onBindViewHolder(UserViewHolder holder, int position) {
User user = mDiffer.getCurrentList().get(position);
holder.bindTo(user);
}
public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK
= new DiffUtil.ItemCallback<User>() {
@Override
public boolean areItemsTheSame(
@NonNull User oldUser, @NonNull User newUser) {
// 2つのアイテム自体が同じものであるかを判定します。
return oldUser.getId() == newUser.getId();
}
@Override
public boolean areContentsTheSame(
@NonNull User oldUser, @NonNull User newUser) {
// 表示する内容が前後で異なっているかどうかを判定します。 falseを返す場合Viewが再利用されます。
// このメソッドはareItemsTheSameがtrueを返す場合にのみ呼ばれます。
return oldUser.userName == newUser.userName;
}
}
}
使い方
AsyncListDiffer
-
submitList(list: List<T>)
: AsyncListDifferに新しいリストを渡して非同期で古いリストとの差分を計算させます。 -
getCurrentList()
: 現在のリストを取得します。差分の計算が終わっていない場合は古いリストを返します。
DiffUtil.ItemCallback
-
areItemsTheSame(oldItem: T, newItem: T)
: 2つのアイテムが同じものであるかを判定します(ユニークなIDを比較するなど)。 -
areContentsTheSame(oldItem: T, newItem: T)
:areItemsTheSame
がtrueを返す場合にのみ呼ばれます。2つのアイテムのデータ内容が同じであるかを判定します。RecyclerViewで使用するならば、表示する内容が前後で異なるかどうかを判定することで、Viewの無駄な再生成を防ぐことができます。 -
getChangePayload(oldItem: T, newItem: T)
:areItemsTheSame
がtrue,areContentsTheSame
がfalseを返すときのみに呼ばれます。2つのアイテムを比較して値を返します。例えば、画像のurlに変更があったときだけ画像表示時にフェードアニメーションをかけ、そうでないときはアニメーションをかけたくないときなどに用いると便利です。