English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Утилита DiffUtil в Android 7.0: подробное объяснение

1. Обзор

DiffUtil - это новый инструмент из support-v7:24.2.0, который используется для сравнения двух наборов данных и поиска минимального объема изменений от старого набора данных до нового набора данных.

Когда речь заходит о наборе данных,我相信 вы знаете, с чем это связано, это моя любимая вещь, RecyclerView.

На мой взгляд, самое большое его назначение - это не использовать безумно mAdapter.notifyDataSetChanged() при обновлении RecyclerView.

У старого метода безумного использования mAdapter.notifyDataSetChanged() есть два недостатка:

1. Не вызывается анимация RecyclerView (удаление, добавление, перемещение, анимация изменения)

2. Низкая производительность,毕竟 это просто обновление всего RecyclerView, в крайних случаях: новый и старый набор данных абсолютно идентичны, эффективность самая низкая.

После использования DiffUtil код изменяется следующим образом:

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);

Он автоматически рассчитывает различия между новым и старым набором данных и в зависимости от этого автоматически вызывается следующие четыре метода

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);

Очевидно, что эти четыре метода выполняются с анимацией RecyclerView, и все они являются методами направленного обновления, что значительно повышает эффективность обновления.
Как всегда, сначала上图:

На рисунке 1 показан эффект безумного использования mAdapter.notifyDataSetChanged(), можно увидеть, что обновление происходит очень жестко, элементы suddenly появляются на определенном месте:

На рисунке 2 показан эффект использования DiffUtils, наиболее明显 то, что есть анимация вставки и перемещения элементов Item:

Конечно, GIF выглядит не очень хорошо, лучше всего скачать Demo в конце статьи и запустить его, чтобы увидеть результат.

В этой статье будет включено и не только включено следующее содержимое:

1 Предлагаю сначала рассказать о простом использовании DiffUtil, чтобы реализовать эффект «инкрементального обновления» при刷新 (называю это我自己).
2 Высокий уровень использования DiffUtil, когда у элемента Item только изменяется содержимое (data), а положение (position) не изменяется, выполняется частичное обновление (официально называется Partial bind, частичная привязка).
3 Изучение метода public void onBindViewHolder(VH holder, int position, List<Object> payloads) класса RecyclerView.Adapter и освоение его.
4 Вычислять DiffResult в фоновом потоке, обновлять RecyclerView в главном потоке.
5 Как удалить анимацию белого света, вызванную notifyItemChanged() у части пользователей.
6 Хинайзирование официальных комментариев к классам и методам DiffUtil

2 Пример использования DiffUtil

Ранее уже упоминалось, что DiffUtil помогает нам при обновлении RecyclerView рассчитывать различия между новыми и старыми наборами данных и автоматически вызывать метод обновления RecyclerView.Adapter, чтобы обеспечить эффективное обновление с анимацией элементов.

До того как мы начнем его изучать, нам нужно сделать некоторые подготовительные работы. Напишем Demo с простым青年版, безмозглым вызовом notifyDataSetChanged() для обновления.

1 Обычный JavaBean, но реализующий метод clone, используется только для создания Demo для имитации обновлений, на практике не нужен, так как данные загружаются из Интернета при обновлении.

class TestBean implements Cloneable {
 private String name;
 private String desc;
 ....//Методы get set опущены
 //Только DEMO. Реализация метода клонирования
 @Override
 public TestBean clone() throws CloneNotSupportedException {
  TestBean bean = null;
  try {
   bean = (TestBean) super.clone();
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
  return bean;
 }

2 Реализация обычного RecyclerView.Adapter.

public class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH> {
 private final static String TAG = "zxt";
 private List<TestBean> mDatas;
 private Context mContext;
 private LayoutInflater mInflater;
 public DiffAdapter(Context mContext, List<TestBean> mDatas) {
  this.mContext = mContext;
  this.mDatas = mDatas;
  mInflater = LayoutInflater.from(mContext);
 }
 public void setDatas(List<TestBean> mDatas) {
  this.mDatas = mDatas;
 }
 @Override
 public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) {
  return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false));
 }
 @Override
 public void onBindViewHolder(final DiffVH holder, final int position) {
  TestBean bean = mDatas.get(position);
  holder.tv1.setText(bean.getName());
  holder.tv2.setText(bean.getDesc());
  holder.iv.setImageResource(bean.getPic());
 }
 @Override
 public int getItemCount() {
  return mDatas != null ? mDatas.size() : 0;
 }
 class DiffVH extends RecyclerView.ViewHolder {
  TextView tv1, tv2;
  ImageView iv;
  public DiffVH(View itemView) {
   super(itemView);
   tv1 = (TextView) itemView.findViewById(R.id.tv1);
   tv2 = (TextView) itemView.findViewById(R.id.tv2);
   iv = (ImageView) itemView.findViewById(R.id.iv);
  }
 }
}

3 Код Activity:

public class MainActivity extends AppCompatActivity {
 private List<TestBean> mDatas;
 private RecyclerView mRv;
 private DiffAdapter mAdapter;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  initData();
  mRv = (RecyclerView) findViewById(R.id.rv);
  mRv.setLayoutManager(new LinearLayoutManager(this));
  mAdapter = new DiffAdapter(this, mDatas);
  mRv.setAdapter(mAdapter);
 }
 private void initData() {
  mDatas = new ArrayList<>();
  mDatas.add(new TestBean("张旭童1", "Android", R.drawable.pic1));
  mDatas.add(new TestBean("张旭童2", "Java", R.drawable.pic2));
  mDatas.add(new TestBean("张旭童3", "背锅", R.drawable.pic3));
  mDatas.add(new TestBean("张旭童4", "手撕产品", R.drawable.pic4));
  mDatas.add(new TestBean("张旭童5", "手撕测试", R.drawable.pic5));
 }
 /**
  * имитация операции обновления
  *
  * @param view
  */
 public void onRefresh(View view) {
  try {
   List<TestBean> newDatas = new ArrayList<>();
   for (TestBean bean : mDatas) {
    newDatas.add(bean.clone());//копирование старых данных, имитация операции обновления
   }
   newDatas.add(new TestBean("赵子龙", "帅", R.drawable.pic6));//добавление новых данных
   newDatas.get(0).setDesc("Android+");
   newDatas.get(0).setPic(R.drawable.pic7); // имитация изменения данных
   TestBean testBean = newDatas.get(1); // имитация перемещения данных
   newDatas.remove(testBean);
   newDatas.add(testBean);
   //Не забудьте предоставить новые данные Adapter
   mDatas = newDatas;
   mAdapter.setDatas(mDatas);
   mAdapter.notifyDataSetChanged(); // Раньше мы могли только так
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
 }
}

Это просто, но при создании новой источника данных newDatas мы проходим по старому источнику данных mDatas, вызывая метод clone() для каждого data, чтобы убедиться, что новые и старые источники данных имеют одинаковые данные, но разные адреса памяти (указатели), чтобы при изменении значений в newDatas не затрагивались значения в mDatas.

4 activity_main.xml Удалены некоторые коды размеров, осталось только RecyclerView и Button для имитации обновления.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
>
 <android.support.v7.widget.RecyclerView
  android:id="@+id/rv" />
 <Button
  android:id="@+id/btnRefresh"
  android:layout_alignParentRight="true"
  android:onClick="onRefresh"
  android:text="Мокрая загрузка" />
</RelativeLayout>

Это обычный пример, который легко написать молодой человек, бездумно используя notifyDataSetChanged(), результаты работы показаны на рисунке 1 в первой главе.
Но все мы хотим быть культурной молодежью, поэтому

Теперь начинаем с главного, для простого использования DiffUtil нам нужно всего лишь дописать один класс.

Чтобы стать интеллигентной молодежью, нужно реализовать класс, наследующий от DiffUtil.Callback, и реализовать его четыре abstract метода.
Хотя этот класс называется Callback, лучше понять его как класс, определяющий договоры (Contract) и правила (Rule) для сравнения новых и старых Item.

Абстрактный класс DiffUtil.Callback таков:

 public abstract static class Callback {
  public abstract int getOldListSize();//Размер старого набора данных
  public abstract int getNewListSize();//Размер нового набора данных
  public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//Является ли Item в старом и новом наборе данных в одной и той же позиции одним и тем же объектом? (Содержимое может отличаться, если здесь вернется true, будет вызван следующий метод)
  public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//Эта метод вызывается только в том случае, если метод above возвращает true, моя интерпретация такова, что только notifyItemRangeChanged() вызывает его, определяет, изменилось ли содержимое item
  //Этот метод используется в продвинутых методах DiffUtil, о нем пока не будем говорить
  @Nullable
  public Object getChangePayload(int oldItemPosition, int newItemPosition) {
   return null;
  }
 }

  Этот Demo реализует DiffUtil.Callback, основные методы配有中俄双语注释(просто говоря, перевел официальные английские комментарии, чтобы让大家更好地 понять).

/**
 * Описание: Основной класс, используемый для определения того, равны ли новые и старые Item.
 * Автор: zhangxutong
 * Электронная почта: [email protected]
 * Время: 2016/9/12.
 */
public class DiffCallBack extends DiffUtil.Callback {
 private List<TestBean> mOldDatas, mNewDatas;// по имени
 public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) {
  this.mOldDatas = mOldDatas;
  this.mNewDatas = mNewDatas;
 }
 // размер старого набора данных
 @Override
 public int getOldListSize() {
  return mOldDatas != null ? mOldDatas.size() : 0;
 }
 // размер нового набора данных
 @Override
 public int getNewListSize() {
  return mNewDatas != null ? mNewDatas.size() : 0;
 }
 /**
  * Вызывается DiffUtil для решения вопроса о том, представляют ли два объекта один и тот же Item.
  * Вызывается DiffUtil для определения того, являются ли два объекта одним и тем же Item.
  * Например, если ваши элементы имеют уникальные идентификаторы, этот метод должен проверить их идентичность.
  * Например, если ваш элемент имеет уникальное поле id, этот метод проверяет, равны ли они.
  * В этом примере проверяется, одинакова ли строка name
  *
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition Позиция элемента в новом списке
  * @return True, если два элемента представляют один и тот же объект, или false, если они различаются.
  */
 @Override
 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
  return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
 }
 /**
  * Вызывается DiffUtil, когда он хочет проверить, имеют ли два элемента одинаковые данные.
  * Вызывается DiffUtil для проверки того, содержат ли два элемента одинаковые данные
  * DiffUtil использует эту информацию для обнаружения изменений в содержании элемента.
  * DiffUtil использует информацию, возвращаемую (true false), для проверки того, изменилось ли содержимое текущего элемента
  * DiffUtil использует этот метод для проверки равенства вместо {@link Object#equals(Object)}
  * DiffUtil использует этот метод для проверки равенства вместо метода equals.
  чтобы вы могли изменить его поведение в зависимости от вашего интерфейса.
  * Поэтому вы можете изменить его возвращаемое значение в зависимости от вашего интерфейса.
  * Например, если вы используете DiffUtil с
  * Для адаптера {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, вам следует
  * возвращайте, являются ли визуальные представления элементов одинаковыми.
  * Например, если вы используете RecyclerView.Adapter в сочетании с DiffUtil, вам нужно вернуть, являются ли визуальные представления элементов одинаковыми.
  * Этот метод вызывается только если метод {@link #areItemsTheSame(int, int)} возвращает
  * {@code true} для этих элементов.
  * Этот метод вызывается только если метод areItemsTheSame() возвращает true.
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list which replaces the
  *      oldItem
  * @return True if the contents of the items are the same or false if they are different.
  */
 @Override
 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
  TestBean beanOld = mOldDatas.get(oldItemPosition);
  TestBean beanNew = mNewDatas.get(newItemPosition);
  if (!beanOld.getDesc().equals(beanNew.getDesc())) {
   return false; //Если содержимое различается, верните false
  }
  if (beanOld.getPic() != beanNew.getPic()) {
   return false; //Если содержимое различается, верните false
  }
  return true; //По умолчанию два содержимого data одинаковы
 }

Комментариев написано так много и код так прост, что его можно понять одним взглядом.

Затем, когда вы используете его, закомментируйте метод notifyDatasetChanged(), который вы писали раньше, и замените его следующим кодом:

//Новый любимец интеллигентных молодых людей
//Используйте метод calculateDiff() DiffUtil, передавая в него объект правила DiffUtil.Callback и переменную boolean, указывающую на обнаружение перемещения item, чтобы получить объект DiffUtil.DiffResult
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
//Используйте метод dispatchUpdatesTo() объекта DiffUtil.DiffResult, передавая его в Adapter RecyclerView, чтобы стать интеллигентным青年ом
diffResult.dispatchUpdatesTo(mAdapter);
//Не забудьте предоставить новые данные Adapter
mDatas = newDatas;
mAdapter.setDatas(mDatas);

Объяснение:

Шаг 1

Прежде чем установить newDatas в Adapter, сначала вызовите метод DiffUtil.calculateDiff()Расчет минимального набора обновлений, преобразующего старые и новые данные, это

Объект DiffUtil.DiffResult
Метод DiffUtil.calculateDiff() определен следующим образом:
Первый параметр - это объект DiffUtil.Callback
Второй параметр означает, следует ли обнаруживать перемещение элементов Item, установите false для повышения эффективности алгоритма, настраивайте по мере необходимости, у нас это true.

public static DiffResult calculateDiff(Callback cb, boolean detectMoves)

Шаг два

Затем используйте метод dispatchUpdatesTo() объекта DiffUtil.DiffResult,传入 RecyclerView.Adapter, чтобы заменить метод notifyDataSetChanged() mAdapter, используемый обычными молодыми людьми.

Из просмотра исходного кода следует, что该方法 внутренне вызывает четыре метода направленного обновления адаптера.

 public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) {
   dispatchUpdatesTo(new ListUpdateCallback() {
    @Override
    public void onInserted(int position, int count) {
     adapter.notifyItemRangeInserted(position, count);
    }
    @Override
    public void onRemoved(int position, int count) {
     adapter.notifyItemRangeRemoved(position, count);
    }
    @Override
    public void onMoved(int fromPosition, int toPosition) {
     adapter.notifyItemMoved(fromPosition, toPosition);
    }
    @Override
    public void onChanged(int position, int count, Object payload) {
     adapter.notifyItemRangeChanged(position, count, payload);
    }
   });
  }

Краткое резюме:

Таким образом, DiffUtil не только может быть использован с RecyclerView, но и мы можем реализовать четыре метода интерфейса ListUpdateCallback, чтобы сделать что-то.(Я временно не несу ответственности за любое из них, может быть, это будет полезно для配合控件 девяти ячеек в моем проекте? Или улучшить NestFullListView, о котором я писал в моей последней статье? Маленькая рекомендация, см. решение для вложения ListView, RecyclerView и ScrollView: http://blog.csdn.net/zxt0601/article/details/52494665)

До этого момента мы эволюционировали в интеллигентную молодежь, эффект работы и рис. 2 раздела 1 будут очень похожи.

Единственное различие в том, что в этот момент adapter.notifyItemRangeChanged() будет иметь анимацию обновления Item, которая светится (positionDemo равен 0 для item). Эта анимация света нравится кому-то, не нравится кому-то, но это не важно,

Потому что, когда мы naucились продвинутого использования DiffUtil в разделе 3, вам нравится или нет анимация ItemChange, она уйдет с ветром. (Не знаю, это ли ошибка от разработчиков)
Эффект будет таким, как показано на рисунке 2 в разделе 1, наш item0 действительно изменился как изображение, так и текст, но эта измена не сопровождалась никаким анимацией.

Пусть нас приведет на путь в мире интеллигентной молодежи.

Три продвинутых использования DiffUtil

Теория:

Продвинутый способ использования涉及到 только два метода,
Нам нужно реализовать методы DiffUtil.Callback
Метод public Object getChangePayload(int oldItemPosition, int newItemPosition)
Возвращаемый Object указывает на то, какие содержимое Item изменилось.

В сочетании сRecyclerView.Adapter
Метод public void onBindViewHolder(VH holder, int position, List<Object> payloads)
Готово, завершаем定向 обновление.
Подчеркнем, это новый метод, обратите внимание, что у него три параметра, первые два нам знакомы, а третий параметр содержит Object, который мы возвращаем в getChangePayload().

Итак, давайте сначала посмотрим, кто такой этот метод:

В исходном коде v7-24.2.0 он выглядит так:

 /**
   * Вызывается RecyclerView для отображения данных в указанной позиции. Этот метод
   * следует обновить содержимое {@link ViewHolder#itemView}, чтобы отразить элемент в
   * указанной позиции.
   * <p>
   * Примечание: в отличие от {@link android.widget.ListView}, RecyclerView не вызовет этот метод
   * снова, если положение элемента изменяется в наборе данных,unless the item itself is
   * аннулирован или новая позиция не может быть определена. По этой причине, вы должны использовать
   * используйте параметр <code>position</code> при получении связанных данных внутри
   * этим методом и не следует хранить его копию. Если вам нужно получить положение элемента позже
   * в (например, в листенере щелчка), используйте {@link ViewHolder#getAdapterPosition()}, который будет
   * иметь обновленную позицию адаптера.
   * <p>
   * Частичное связывание против полного связывания:
   * <p>
   
   * {@link #notifyItemRangeChanged(int, int, Object)}. Если список загружаемых контентов не пуст,
   * ViewHolder в настоящее время связан с старыми данными и Адаптер может выполнить эффективное частичное
   * обновление с использованием информации о загружаемом контенте. Если загружаемый контент пуст, Адаптер должен выполнить полное связывание.
   * Адаптер не должен предполагать, что загружаемый контент, передаваемый в методах уведомления, будет получен
   * onBindViewHolder(). Например, когда вид не прикреплен к экрану,
   * нагрузки в notifyItemChange() будут просто удалены.
   *
   * @param holder ViewHolder, который должен быть обновлен для представления содержимого
   * элемент в указанной позиции в наборе данных.
   * @param position Позиция элемента в наборе данных адаптера.
   * @param payloads Несущая нагрузку некорректная список. Может быть пустым списком, если требует полного
   * обновить.
   */
  public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
   onBindViewHolder(holder, position);
  }

Оказывается, внутри он просто вызывает onBindViewHolder(holder, position) с двумя параметрами (впрочем, это лирика, эй, у моего NestFullListView Adapter также есть несколько схожих моментов,看来 я становлюсь ближе к Google Богу).

Вот тут я понял, что вход в onBind на самом деле этот метод, он является методом, соответствующим onCreateViewHolder,
Прокрутив код向下 несколько строк, можно увидеть, что есть public final void bindViewHolder(VH holder, int position), которая внутри вызывает трехпараметричный onBindViewHolder.
О RecyclerView.Adapter не так просто объяснить в нескольких словах. (На самом деле, я знаю только до этого уровня).
Теперь не будем отклоняться от темы, вернемся к нашему методу bindViewHolder(VH holder, int position, List<Object> payloads) с тремя параметрами, в котором много англоязычных комментариев, чтение которых, на мой взгляд, очень полезно для понимания метода, поэтому я перевел их.

Перевод:}}

Вызовается RecyclerView для отображения данных в указанном положении.
Этот метод должен обновить содержимое ItemView в ViewHolder, чтобы отразить изменения в данном положении Item.
Обратите внимание, в отличие от ListView, если данные item в данном положении изменились, RecyclerView не будет вызывать этот метод снова,除非 item сам стал invalidated или нового положения не можно определить.
Из этой причины, в этом методе, вы должны использовать только параметр position для получения связанных данных item, и не следует сохранять копию этого данных item.
Если вам нужно稍 позже получить положение этого item, например, для установки clickListener, используйте ViewHolder.getAdapterPosition(), которая предоставляет обновленное положение.
(Двоеточие, я вижу здесь, что это объяснение двухпараметрового метода onbindViewHolder).
Ниже приведена уникальная часть этой трехпараметровой методики:)
**Частичная (partial) привязка** vs полная (full) привязка
Параметр payloads - это объединенный список, полученный из notifyItemChanged(int, Object) или notifyItemRangeChanged(int, int, Object).
Если список payloads не пуст, то текущий ViewHolder, привязанный к старым данным и Adapter, могут использовать данные payload для выполнения быстрого обновления части.
Если payload пуст, Adapter должен выполнить полную привязку (вызов метода с двумя параметрами).
Adapter не должен предполагать (думать без доказательств), что payload, передаваемый в notifyxxxx уведомлениях, обязательно будет получен в методе onBindViewHolder(). (Это предложение трудно перевести QAQ, смотрите примеры).
Например, если View не Attached на экране, этот payload из notifyItemChange() можно просто выбросить.
Объект payloads не может быть null, но он может быть пустым (empty), в этом случае потребуется полная привязка (поэтому в методе мы просто проверяем isEmpty, без необходимости повторного проверки на пустоту).
Автор: Этот метод является эффективным методом. Я являюсь неэффективным переводчиком, и мне потребовалось более 40 минут, чтобы понять, что важные части выделены жирным.

Practice:

All this talk, in fact, it's super simple to use:
Let's first see how to use the getChangePayload() method, with both Chinese and English comments

  

 /**
  * returns false for them, DiffUtil
  * When {@link #areItemsTheSame(int, int)} returns true for two items and
  * calls this method to get a payload about the change.
  * 
  * When {@link #areItemsTheSame(int, int)} returns true, and {@link #areContentsTheSame(int, int)} returns false, DiffUtils will call this method to get a payload about the change.
  * to get the payload of the changes of this item.
  * 
  * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
  * particular field that changed in the item and your
  * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
  * information to run the correct animation.
  * 
  * For example, if you use RecyclerView with DiffUtils, you can return this
  * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
  * 
  * Default implementation returns null. * По умолчанию реализация возвращает null
  *
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition Позиция элемента в новом списке
  * @return Payload объект, представляющий изменения между двумя элементами.
  * Возвращает payload объекта, представляющего изменения между двумя элементами.
  */
 @Nullable
 @Override
 public Object getChangePayload(int oldItemPosition, int newItemPosition) {
  // Реализация этого метода делает вас настоящим интеллигентом интеллигентов
  // Часть обновления направленного обновления
  // Наиболее высокая эффективность
  // Просто нет анимации белого света ItemChange, (даже я считаю, что это не очень важно)
  TestBean oldBean = mOldDatas.get(oldItemPosition);
  TestBean newBean = mNewDatas.get(newItemPosition);
  // Здесь не нужно сравнивать основные поля, они всегда равны
  Bundle payload = new Bundle();
  if (!oldBean.getDesc().equals(newBean.getDesc())) {
   payload.putString("KEY_DESC", newBean.getDesc());
  }
  if (oldBean.getPic() != newBean.getPic()) {
   payload.putInt("KEY_PIC", newBean.getPic());
  }
  if (payload.size() == 0) // Если изменений нет, передается пустой набор
   return null;
  return payload; //
 }

Кратко говоря, этот метод возвращает объект типа Object, который содержит измененные содержимое некоторых элементов.
Мы здесь используем Bundle для сохранения этих изменений.

В Adapter нужно переопределить три параметра метода onBindViewHolder:

 @Override
 public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) {
  if (payloads.isEmpty()) {}}
   onBindViewHolder(holder, position);
  } else {
   //Литературные молодые люди
   Bundle payload = (Bundle) payloads.get(0);
   TestBean bean = mDatas.get(position);
   for (String key : payload.keySet()) {
    switch (key) {
     case "KEY_DESC":
      //Здесь можно использовать данные payload, но также можно использовать
      holder.tv2.setText(bean.getDesc());
      break;
     case "KEY_PIC":
      holder.iv.setImageResource(payload.getInt(key));
      break;
     по умолчанию:
      break;
    }
   }
  }
 }

Переданные payloads представляют собой список, как следует из комментариев, он никогда не будет null, поэтому мы проверяем, является ли он empty
Если он empty, вызывается функция с двумя параметрами, выполняется один раз Full Bind.
Если он не empty, выполняется partial bind
Через индекс 0 мы берем payload, который мы возвращаем в методе getChangePayload, затем итерируем ключи payload и выполняем поиск по ключу, если payload содержит соответствующие изменения, их извлекают и обновляют на ItemView.
(Здесь, данные, полученные через mDatas, также являются данными последнего источника данных, поэтому обновление может быть выполнено с помощью данных payload или новых данных).

До этого момента мы уже освоили обновление RecyclerView, наиболее литературное из всех литературных молодых людей.

Четыре. Использование DiffUtil в под线程е

В заголовке комментария к исходному коду DiffUtil介绍DiffUtil.
DiffUtil использует алгоритм различия Eugene W. Myers, но этот алгоритм не может детектировать перемещение элементов, поэтому Google улучшил его, чтобы поддерживать детекцию перемещающихся элементов, но это потребляет больше производительности.
При наличии 1000 элементов данных и 200 изменений, время выполнения этой алгоритма:
Когда включена детекция движения: среднее значение: 27,07мс, медиана: 26,92мс.
Когда отключена детекция движения: среднее значение: 13,54мс, медиана: 13,36мс.
Интересующиеся могут сами прочитать комментарии в начале исходного кода, для нас полезно то, что в одном из них提到,
Если наш список слишком велик, время вычисления DiffResult может быть довольно долгим, поэтому我们应该 поместить процесс получения DiffResult в фоновый поток и обновить RecyclerView в главном потоке.

Здесь я использую Handler в сочетании с DiffUtil:

Код如下:

 private static final int H_CODE_UPDATE = 1;
 private List<TestBean> mNewDatas;//Добавить переменную для временного хранения newList
 private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case H_CODE_UPDATE:
     //Получить Result
     DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
     diffResult.dispatchUpdatesTo(mAdapter);
     //Не забудьте предоставить новые данные Adapter
     mDatas = mNewDatas;
     mAdapter.setDatas(mDatas);
     break;
   }
  }
 });
   new Thread(new Runnable() {
    @Override
    public void run() {
     //Расчет DiffResult выполняется в фоновом потоке
     DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true);
     Message message = mHandler.obtainMessage(H_CODE_UPDATE);
     message.obj = diffResult;//obj хранит DiffResult
     message.sendToTarget();
    }
   }).start();

Это просто использование Handler, не будем повторяться.

Пять итоги и другие

1 На самом деле код в этой статье очень мал, вы можете скачать Demo для просмотра, всего четыре класса.
но неосознанно я написал так много, в основном涉及 перевод комментариев в исходном коде, чтобы всем было легче понять.

2 DiffUtil очень подходит для таких сценариев, как下拉 обновление,
Эффективность обновления увеличилась, и есть анимация, и ~ вам даже не нужно думать.
но если нужно только сделать удаление, лайк и т.д.,完全可以不用DiffUtils. Просто запомните position, проверьте, находится ли position в экране, и вызовите несколько методов направленной перезагрузки.

3 Фактически, DiffUtil не может использоваться только с RecyclerView.Adapter,
Мы можем реализовать интерфейс ListUpdateCallback ourselves, используя DiffUtil, чтобы найти минимальный набор различий между новым и старыми наборами данных, и сделать больше.

4 Внимание! При написании DEMO, новый и старый данные для сравнения, не только ArrayList различаются, но и каждый data в них также должен быть различным. В противном случае не будет вызван changed.
На самом деле в проекте не встречается, потому что новые данные часто поступают из сети.

5 Сегодня последний день中秋节, и моя компания начала работать!!! В гневе я написал статью о DiffUtil, и я не использую DiffUtil, но легко могу сравнить наши компании с другими. QAQ, и сегодня мой настрой не был хорошим, и мне потребовалось 8 часов, чтобы завершить его. Я думал, что эта статья может быть включена в сборник микроэссе, но она оказалась довольно длинной. Если у вас нет терпения, вы можете загрузить DEMO и посмотреть, кода немного, и им легко пользоваться.

Групповой портал github:
https://github.com/mcxtzhang/DiffUtils

Вот и все, что касается материалов по инструменту DiffUtil для Android 7.0. В будущем продолжим добавлять соответствующие материалы, спасибо за поддержку нашего сайта!

Основной учебник
Вам может понравиться