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

Android7Clase de herramientas: Descripción detallada de DiffUtil

一 概述

DiffUtil es soportado-v7:24.2La nueva herramienta de clase en .0, se utiliza para comparar dos conjuntos de datos, buscando el conjunto de datos antiguo-》新数据集的最小变化量。

Hablando del conjunto de datos, estoy seguro de que todos saben a qué está relacionado, es mi favorito, RecyclerView.

En los días que he estado usando, su mayor utilidad es evitar el uso innecesario de mAdapter.notifyDataSetChanged() al actualizar RecyclerView.

Antes, el método mAdapter.notifyDataSetChanged() tenía dos desventajas:

1.No desencadena las animaciones de RecyclerView (eliminación, adición, cambio de posición, animación de cambio)

2.La performance es baja, ya que actualiza sin pensar todo el RecyclerView, en casos extremos: los conjuntos de datos nuevos y antiguos son idénticos, la eficiencia es la más baja.

Después de usar DiffUtil, cambia al siguiente código:

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

Automáticamente calculará la diferencia entre los conjuntos de datos nuevos y antiguos y llamará automáticamente a los siguientes cuatro métodos según la situación de la diferencia.

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

Claramente, estos cuatro métodos se ejecutan con animaciones de RecyclerView y son métodos de actualización定向, lo que mejora la eficiencia de actualización.
Siguiendo la regla, primero mostramos la imagen,

La imagen uno muestra el efecto de notifyDataSetChanged() sin pensar, se puede ver que la actualización es muy brusca y los items aparecen de repente en algún lugar:

La imagen dos muestra el efecto de DiffUtils, lo más notable es que hay animaciones de inserción y movimiento de items:

La conversión a GIF es un poco mala, descarga el demo al final para ver el efecto.

Este artículo incluirá y no solo incluirá lo siguiente:

1 Primero introduciré el uso simple de DiffUtil, que realiza una actualización incremental durante la actualización (llamado 'actualización incremental' por mí mismo).
2 Uso avanzado de DiffUtil, que completa la actualización parcial (oficialmente llamada Partial bind, parte de la unión) cuando solo cambia el contenido (data) del item y no cambia la posición (position).
3 Conocer y dominar el método public void onBindViewHolder(VH holder, int position, List<Object> payloads) de RecyclerView.Adapter.
4 Calcular DiffResult en un hilo secundario y actualizar RecyclerView en el hilo principal.
5 Cómo deshacerse del animación de Item que hace que el Item brille por un momento, que no le gusta a algunos, usando notifyItemChanged().
6 Traducción de los comentarios oficiales de las clases y métodos de DiffUtil

Uso simple de DiffUtil

Como también se mencionó anteriormente, DiffUtil nos ayuda a calcular las diferencias entre los conjuntos de datos nuevos y antiguos al actualizar RecyclerView, y llama automáticamente al método de actualización del RecyclerView.Adapter para completar una actualización eficiente con efectos de animación de items.

Entonces, antes de aprenderlo, debemos hacer algunas preparaciones, primero escribir una versión de demostración para jovenes sin pensar en notifyDataSetChanged() para actualizar.

1 Un JavaBean común, pero que ha implementado el método clone, solo utilizado para escribir Demo simulando actualizaciones, que no se necesita en proyectos reales, ya que los datos se extraen de la red durante la actualización.:

class TestBean implements Cloneable {
 private String name;
 private String desc;
 ....//Métodos get y set omitidos
 //Sólo para DEMO, implementar el método de clonación.
 @Override
 public TestBean clone() throws CloneNotSupportedException {
  TestBean bean = null;
  try {
   bean = (TestBean) super.clone();
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
  return bean;
 }

2 Implementar un RecyclerView.Adapter común.

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 != null63; 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 Código de la actividad:

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"Cargar la responsabilidad", R.drawable.pic3));
  mDatas.add(new TestBean("张旭童"4"Desgarrar producto", R.drawable.pic4));
  mDatas.add(new TestBean("张旭童"5"Desgarrar prueba", R.drawable.pic5));
 }
 /**
  * Operación de actualización simulada
  *
  * @param view
  */
 public void onRefresh(View view) {
  try {
   List<TestBean> newDatas = new ArrayList<>();
   for (TestBean bean : mDatas) {
    newDatas.add(bean.clone());//clonar los datos antiguos una vez, simular la operación de refresco
   }
   newDatas.add(new TestBean("Zhao Zilong", "Guapo", R.drawable.pic6));//Simulación de adición de datos
   newDatas.get(0).setDesc("Android+");
   newDatas.get(0).setPic(R.drawable.pic7);//Simulación de modificación de datos
   TestBean testBean = newDatas.get(1);//Simulación de desplazamiento de datos
   newDatas.remove(testBean);
   newDatas.add(testBean);
   //No olvide proporcionar los nuevos datos al Adapter
   mDatas = newDatas;
   mAdapter.setDatas(mDatas);
   mAdapter.notifyDataSetChanged();//Anteriormente, la mayoría de las veces solo podíamos hacerlo así
  } catch (CloneNotSupportedException e) {
   e.printStackTrace();
  }
 }
}

Es bastante simple, solo que al construir el nuevo conjunto de datos newDatas, se recorre el conjunto de datos antiguo mDatas, se llama al método clone() de cada data, para asegurar que aunque los conjuntos de datos nuevos y antiguos tengan datos consistentes, las direcciones de memoria (punteros) no sean las mismas. De esta manera, al modificar los valores de newDatas más adelante, no se afectarán los valores de mDatas.

4 activity_main.xml Eliminaron algunos códigos de anchura y altura, solo hay un RecyclerView y un Button para simular el refresco.:

<?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="Simular actualización" />
</RelativeLayout>

El siguiente es un demo fácilmente escrito por un joven común, que utiliza notifyDataSetChanged() sin pensarlo, el efecto de ejecución se muestra en la figura 1 del primer capítulo.
Pero todos queremos ser jóvenes literarios, así que

A continuación, entramos en materia, para usar DiffUtil de manera simple, solo necesitamos escribir una clase adicional.

Para convertirse en un joven literario, necesitamos implementar una clase que herede de DiffUtil.Callback e implementar sus cuatro métodos abstractos.
Aunque este clase se llama Callback, es más apropiado entenderla como: una clase que define contratos (Contract) y reglas (Rule) para comparar si los Item nuevos y antiguos son iguales.

La clase abstracta DiffUtil.Callback es la siguiente:

 public abstract static class Callback {
  public abstract int getOldListSize();//tamaño del conjunto de datos antiguos
  public abstract int getNewListSize();//tamaño del conjunto de datos nuevos
  public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//¿Los Item en la misma posición de los conjuntos de datos nuevos y antiguos son objetos? (Puede que el contenido sea diferente, si aquí devuelve true, se llamará al siguiente método)
  public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//Este método solo se llama cuando el método anterior devuelve true, mi comprensión es que solo notifyItemRangeChanged() llama, para determinar si el contenido del item ha cambiado
  //Este método se utiliza en el uso avanzado de DiffUtil, no se mencionará por ahora
  @Nullable
  public Object getChangePayload(int oldItemPosition, int newItemPosition) {
   return null;
  }
 }

  Este Demo realiza DiffUtil.Callback, los métodos nucleares vienen con comentarios en chino e inglés (lo que significa que se han traducido los comentarios oficiales en inglés, lo que facilita una mejor comprensión para todos).

/**
 * Introducción: Clase nuclear utilizada para determinar si los Item nuevos y antiguos son iguales
 * Autor: zhangxutong
 * Correo electrónico: [email protected]
 * Tiempo: 2016/9/12.
 */
public class DiffCallBack extends DiffUtil.Callback {
 private List<TestBean> mOldDatas, mNewDatas;//Mira el nombre
 public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) {
  this.mOldDatas = mOldDatas;
  this.mNewDatas = mNewDatas;
 }
 //tamaño del conjunto de datos antiguos
 @Override
 public int getOldListSize() {
  devuelve mOldDatas != null ? mOldDatas.size() : 0;
 }
 //tamaño del conjunto de datos nuevos
 @Override
 public int getNewListSize() {
  devuelve mNewDatas != null ? mNewDatas.size() : 0;
 }
 /**
  * Llamado por DiffUtil para decidir si dos objetos representan el mismo Item.
  * Llamado por DiffUtil para determinar si dos objetos son el mismo Item.
  * Por ejemplo, si sus elementos tienen ids únicos, este método debe verificar la igualdad de sus ids.
  * Por ejemplo, si su Item tiene un campo id único, este método debe determinar si el id es igual.
  * Este ejemplo determina si el campo name es idéntico
  *
  * @param oldItemPosition La posición del item en la lista antigua
  * @param newItemPosition La posición del item en la lista nueva
  * @return True si los dos elementos representan el mismo objeto o false si son diferentes.
  */
 @Override
 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
  devuelve mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
 }
 /**
  * Llamado por DiffUtil cuando desea verificar si dos elementos tienen los mismos datos.
  * 被DiffUtil调用,用于检查两个item是否包含相同的数据
  * DiffUtil使用此信息来检测一个item的内容是否已更改。
  * DiffUtil使用返回的信息(true false)来检测当前item的内容是否发生了变化
  * DiffUtil使用此方法来检查相等性,而不是使用{@link Object#equals(Object)}
  * DiffUtil使用此方法替代equals方法来检查是否相等。
  * 以便你可以根据你的UI改变其行为。
  * 因此你可以根据你的UI来改变它的返回值
  * 例如,如果你使用DiffUtil与
  * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
  * 返回items的视觉表示是否相同。
  * 例如,如果你使用RecyclerView.Adapter配合DiffUtil,你需要返回Item的视觉表现是否相同。
  * 此方法仅在{@link #areItemsTheSame(int, int)}返回
  * {@code true} for these items.
  * 此方法仅在areItemsTheSame()返回true时调用。
  * @param oldItemPosition La posición del item en la lista antigua
  * @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;//Si hay contenido diferente, devuelva false
  }
  if (beanOld.getPic() != beanNew.getPic()) {
   return false;//Si hay contenido diferente, devuelva false
  }
  return true; //El contenido de los dos datos por defecto es el mismo
 }

Hizo comentarios tan detallados+Código simple, confíe en que puede entenderlo a primera vista.

Luego, cuando lo utilice, descomente el método notifyDatasetChanged() que escribió anteriormente y reemplácelo con el siguiente código:

//El nuevo favorito de los jóvenes cultos
//Utilice la método DiffUtil.calculateDiff(), ingrese un objeto DiffUtil.Callback de regla y una variable booleana de detección de movimiento de item, para obtener el objeto DiffUtil.DiffResult.
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
//Utilice el método dispatchUpdatesTo() del objeto DiffUtil.DiffResult, ingrese el Adapter de RecyclerView, y se convierte fácilmente en un joven culto.
diffResult.dispatchUpdatesTo(mAdapter);
//No olvide proporcionar los nuevos datos al Adapter
mDatas = newDatas;
mAdapter.setDatas(mDatas);

Explicación:

Paso uno

Antes de establecer newDatas en el Adapter, primero llame al método DiffUtil.calculateDiff().Calcular el conjunto de actualizaciones más pequeño de la conversión de los conjuntos de datos nuevos y antiguos, es decir

El objeto DiffUtil.DiffResult.
La méthode DiffUtil.calculateDiff() se define como sigue:
El primer parámetro es el objeto DiffUtil.Callback.
El segundo parámetro representa si se detecta el movimiento del Item, cambiándolo a false la eficiencia del algoritmo es mayor, configure según necesidad, aquí es true.

public static DiffResult calculateDiff(Callback cb, boolean detectMoves)

Paso dos

Luego, utilizando el método dispatchUpdatesTo() del objeto DiffUtil.DiffResult, se pasa el Adapter de RecyclerView para reemplazar el método mAdapter.notifyDataSetChanged() utilizado por los jóvenes comunes.

Al revisar el código fuente, podemos ver que该方法内部根据情况调用了adapter的四大定向刷新方法。

 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);
    }
   });
  }

Resumen:

Por lo tanto, DiffUtil no solo puede trabajar con RecyclerView, también podemos implementar las cuatro métodos de la interfaz ListUpdateCallback para hacer algunas cosas. (Por ahora, no me responsabilizo por una opción al azar, ¿podría ser algo que se ajuste a mi controlador de 9 celdas en el proyecto? ¿O optimizar el NestFullListView que escribí en mi artículo anterior? Pequeño anuncio, ver: http://blog.csdn.net/zxt0601/article/details/52494665)

Hasta aquí, hemos evolucionado a jóvenes literarios, el efecto de ejecución es básicamente el mismo que la imagen dos del primer capítulo,

La única diferencia es que en este momento, adapter.notifyItemRangeChanged() tendrá una animación de actualización de Item con luz blanca (el Demo de este artículo tiene un item en la posición 0). Algunos aman esta animación de parpadeo y otros la odian, pero no importa.

Porque cuando aprendemos el uso avanzado de DiffUtil en el tercer capítulo, ya no te importará si te gusta o no el animación de ItemChange, todo se llevará al viento. (No sé si es un bug oficial)
El efecto es como la imagen dos del primer capítulo, nuestro item0 realmente ha cambiado la imagen y el texto, pero este cambio no ha estado acompañado de ningún animación.

Vamos a seguir el camino de los jóvenes literarios en los jóvenes literarios.

Tres usos avanzados de DiffUtil

Teoría:

El uso avanzado solo implica dos métodos,
Necesitamos implementar por separado los métodos de DiffUtil.Callback
El método public Object getChangePayload(int oldItemPosition, int newItemPosition),
El Object devuelto indica qué contenido del Item ha cambiado.

junto con RecyclerView.Adapter
El método public void onBindViewHolder(VH holder, int position, List<Object> payloads),
Completar la actualización定向。
Subrayado, este es un nuevo método, atención, tiene tres parámetros, los dos primeros son conocidos, el tercer parámetro contiene el Object que retornamos en getChangePayload().

Bueno, primero veamos quién es este método:

en v7-24.2.0 del código fuente, tiene este aspecto:

 /**
   * Llamado por RecyclerView para mostrar los datos en la posición especificada. Este método
   * debería actualizar el contenido de la {@link ViewHolder#itemView} para reflejar el elemento en
   * la posición dada.
   * <p>
   * Tenga en cuenta que a diferencia de {@link android.widget.ListView}, RecyclerView no llamará a este método
   * de nuevo si cambia la posición del elemento en el conjunto de datos a menos que el elemento en sí mismo sea
   * inválida o no se puede determinar la nueva posición. Por esta razón, solo debe
   * use el <code>position</mientras se obtiene el elemento de datos relacionado dentro
   * este método y no debe mantener una copia de él. Si necesita la posición de un elemento más tarde
   * en (por ejemplo, en un escuchador de clic), use {@link ViewHolder#getAdapterPosition()} que devolverá
   * tienen la posición del adaptador actualizada.
   * <p>
   * Vinculación parcial vs vinculación completa:
   * <p>
   * El parámetro payloads es una lista de combinación de {@link #notifyItemChanged(int, Object)} o
   * {@link #notifyItemRangeChanged(int, int, Object)}. Si la lista de paquetes no está vacía,
   * El ViewHolder está actualmente vinculado a los datos antiguos y el Adapter puede ejecutar una parte eficiente
   * actualización utilizando la información del payload. Si el payload está vacío, el Adaptador debe ejecutar una unión completa.
   * El adaptador no debe asumir que los payloads pasados en los métodos notify recibirán
   * onBindViewHolder(). Por ejemplo, cuando la vista no está adjunta a la pantalla, el
   * los payloads en notifyItemChange() simplemente se descartarán.
   *
   * @param holder El ViewHolder que debe ser actualizado para representar el contenido del
   *    elemento en la posición dada en el conjunto de datos.
   * @param position La posición del elemento dentro del conjunto de datos del adaptador.
   * @param payloads Un no-lista nula de payloads mezclados. Puede ser una lista vacía si se requiere una unión completa
   *     update.
   */
  public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
   onBindViewHolder(holder, position);
  }

Resulta que internamente solo llama al onBindViewHolder(holder, position) con dos parámetros,(inciso, ¡ay, ay, mi NestFullListView Adapter también tiene un toque similar a esta escritura, parece que estoy más cerca de los grandes de Google)。

Hasta ahora, me he dado cuenta de que, en realidad, el punto de entrada de onBind, es este método, es el que se corresponde con el método onCreateViewHolder.
Al desplazarme algunas líneas hacia abajo en el código, puedo ver que hay un método public final void bindViewHolder(VH holder, int position),que internamente llama al onBindViewHolder de tres parámetros.
Sobre RecyclerView.Adapter, no se puede explicar en pocas palabras. (De hecho, solo he llegado hasta aquí)
Bueno, no me desvié más, volvamos a nuestro método onBindViewHolder(VH holder, int position, List<Object> payloads) de tres parámetros, en la cabecera de este método hay muchas notas en inglés, siempre he pensado que leer estas notas en inglés es muy útil para entender el método, así que las traduje.

Traducción:

Llamado por RecyclerView para mostrar datos en la posición especificada.
Este método debe actualizar el contenido del ItemView en ViewHolder para reflejar los cambios en el item en la posición dada.
Ten en cuenta que, a diferencia de ListView, si los datos del item en la posición dada cambian, RecyclerView no volverá a llamar a este método a menos que el item en sí mismo sea inválido (invalidated) o no se pueda determinar la nueva posición.
Por esta razón, en este método, solo debes usar el parámetro position para obtener los datos del item relacionados y no debes mantener una copia de este item de datos.
Si necesitas la posición de este item más tarde, por ejemplo, para establecer clickListener, debe usar ViewHolder.getAdapterPosition(), que puede proporcionar la posición actualizada.
(El tonto que soy, aquí descubrí que se está explicando el método onbindViewHolder de dos parámetros)
Aquí está la parte única de este método de tres parámetros:)
**vinculación parcial (partial)**vs vinculación completa (full)
El parámetro payloads es una lista combinada obtenida de (notifyItemChanged(int, Object) o notifyItemRangeChanged(int, int, Object)).
Si la lista de payloads no está vacía, el ViewHolder que tiene datos antiguos绑定 y el Adapter actual pueden usar los datos del payload para realizar una actualización eficiente parcial.
Si el payload está vacío, el Adapter debe realizar una completa vinculación (llamada al método de dos parámetros).
El Adapter no debe asumir (pensar sin fundamento) que los payloads que se pasan en las notificaciones notifyxxxx, estarán necesariamente recibidos en el método onBindViewHolder(). (Esta frase es difícil de traducir, aquí está el ejemplo para entenderla)
Por ejemplo, cuando el View no está adjunto en la pantalla, la carga útil proveniente de notifyItemChange() se puede desechar simplemente.
los objetos de carga útil no pueden ser null, pero pueden estar vacíos (empty), en este caso necesitas una conexión completa (por lo que solo tenemos que verificar isEmpty en el método, sin repetir la verificación de nulo).
Comentario del autor: Este método es muy eficiente. Soy un traductor ineficiente, vi40+minutos. Finalmente entendí que la parte importante ya estaba resaltada.

Práctica:

Después de tanto hablar, en realidad es muy simple de usar:
Vamos a ver cómo usar el método getChangePayload() y también incluye comentarios en chino e inglés

  

 /**
  * Cuando {@link #areItemsTheSame(int, int)} devuelve {@code true} para dos elementos y
  * Cuando {@link #areContentsTheSame(int, int)} devuelve false para ellos, DiffUtil
  * llama a este método para obtener una carga útil sobre el cambio.
  * 
  * Cuando {@link #areItemsTheSame(int, int)} devuelve true y {@link #areContentsTheSame(int, int)} devuelve false, DiffUtils llama a este método para obtener una información sobre el cambio.
  * para obtener el payload que ha cambiado en este Item (cuáles son).
  * 
  * Por ejemplo, si estás usando DiffUtil con {@link RecyclerView}, puedes devolver los
  * campo específico que ha cambiado en el item y tus
  * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator puede usar esa
  * información necesaria para ejecutar la animación correcta.
  * 
  * Por ejemplo, si usas RecyclerView junto con DiffUtils, puedes devolver los campos de este Item que han cambiado
  * {@link android.support.v7La clase .widget.RecyclerView.ItemAnimator ItemAnimator puede usar qué información para ejecutar la animación correcta
  * 
  * La implementación por defecto devuelve {@code null}.  * La implementación por defecto es devolver null
  *
  * @param oldItemPosition La posición del item en la lista antigua
  * @param newItemPosition La posición del item en la lista nueva
  * @return Un objeto payload que representa el cambio entre los dos items.
  * Devuelve un objeto payload que representa el cambio entre los dos items.
  */
 @Nullable
 @Override
 public Object getChangePayload(int oldItemPosition, int newItemPosition) {
  //Implementar este método puede hacer que uno sea el más culto entre los jóvenes literarios
  // Actualización parcial en la actualización定向
  // La eficiencia es la más alta
  //Sólo que no hay la animación de luz blanca del ItemChange, (de hecho, también pienso que no es muy importante)
  TestBean oldBean = mOldDatas.get(oldItemPosition);
  TestBean newBean = mNewDatas.get(newItemPosition);
  //Aquí no es necesario comparar los campos principales, deben ser iguales
  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)//Si no hay cambios, se transmite vacío
   return null;
  return payload;//
 }

En términos simples, este método devuelve un objeto de tipo payload que contiene el contenido modificado de algún item.
Aquí utilizamos Bundle para guardar estos cambios.

En el Adapter se reescribe onBindViewHolder de tres parámetros: }}

 @Override
 public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) {
  if (payloads.isEmpty()) {
   onBindViewHolder(holder, position);
  } else {
   //Los jóvenes literarios en los jóvenes literarios
   Bundle payload = (Bundle) payloads.get(0);
   TestBean bean = mDatas.get(position);
   for (String key : payload.keySet()) {
    switch (key) {
     caso "KEY_DESC":
      //Aquí se puede usar los datos del payload, pero también se puede usar
      holder.tv2.setText(bean.getDesc());
      break;
     caso "KEY_PIC":
      holder.iv.setImageResource(payload.getInt(key));
      break;
     por defecto:
      break;
    }
   }
  }
 }

Los payloads pasados aquí son una lista, según los comentarios, no puede ser null, por lo que se juzga si es vacío.
Si está vacío, se llama a una función de dos parámetros para realizar un bind completo.
Si no está vacío, se realiza un bind parcial.
Se extrae el payload devuelto en el método getChangePayload mediante el índice 0, luego se recorre la clave del payload, se realiza una búsqueda según la clave, si el payload contiene cambios correspondientes, se extrae y luego se actualiza en el ItemView.
(Aquí, los datos obtenidos a través de mDatas también son los datos de la fuente más reciente, por lo que se puede actualizar con los datos del payload o los datos nuevos).

Hasta ahora, hemos dominado la actualización de RecyclerView, ese estilo más literario de los jóvenes literarios.

Cuatro: usar DiffUtil en subprocesos

En los comentarios de la cabecera del código fuente de DiffUtil se introduce información sobre DiffUtil.
DiffUtil utiliza el algoritmo de diferencia Eugen W. Myers, pero este algoritmo no puede detectar la movimentación de items, por lo que Google lo mejoró para que pueda detectar movimientos de proyectos, pero detectar movimientos de proyectos consume más recursos.
En1000 elementos de datos,2Al realizar 00 cambios, el tiempo de ejecución de este algoritmo es:
Al abrir la detección de movimiento: promedio:27.07ms, mediana:26.92ms.
Al cerrar la detección de movimiento: promedio:13.54ms, mediana:13.36ms.
Si tiene curiosidad, puede leer los comentarios en la parte superior del código fuente, lo que nos es útil es que uno de ellos menciona:
Si nuestra lista es muy grande, el tiempo requerido para calcular DiffResult es bastante largo, por lo que deberíamos colocar el proceso de obtención de DiffResult en un hilo secundario y actualizar RecyclerView en la línea principal.

Aquí utilizo Handler junto con DiffUtil:

El código es el siguiente:

 private static final int H_CODE_UPDATE = 1;
 private List<TestBean> mNewDatas;//Añadir una variable para almacenar newList
 private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case H_CODE_UPDATE:
     //Extraer Result
     DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
     diffResult.dispatchUpdatesTo(mAdapter);
     //No olvide proporcionar los nuevos datos al Adapter
     mDatas = mNewDatas;
     mAdapter.setDatas(mDatas);
     break;
   }
  }
 });
   new Thread(new Runnable() {
    @Override
    public void run() {
     //Se coloca en un hilo secundario para calcular DiffResult
     DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true);
     Message message = mHandler.obtainMessage(H_CODE_UPDATE);
     message.obj = diffResult;//obj almacena DiffResult
     message.sendToTarget();
    }
   }).start();

Es un uso simple del Handler, no se requiere más explicación.

Cinco resúmenes y otros

1 En realidad, el código de este artículo es muy poco, puedes descargar el Demo para verlo, hay solo cuatro clases.
Pero sin darme cuenta, lo escribí tan largo, principalmente involucrando la traducción de los comentarios del código fuente, para facilitar una mejor comprensión.

2 DiffUtil es muy adecuado para escenarios como el refresco deslizante inferior,
Se mejora la eficiencia de la actualización, y hay animación, y ~ no tienes que pensar por ti mismo.
Pero si solo se hace una eliminación de puntos, no es necesario usar DiffUtils. Mantén el postion, juzga si postion está en la pantalla, llama a varios métodos de actualización direccional.

3 En realidad, DiffUtil no solo se puede usar con RecyclerView.Adapter,
Podemos implementar nosotros mismos el interfaz ListUpdateCallback, utilizando DiffUtil para encontrar el conjunto de diferencias más pequeño entre los conjuntos de datos nuevo y antiguo para hacer más cosas.

4 Atención: al escribir el DEMO, el conjunto de datos nuevo y antiguo utilizado para la comparación, no solo debe ser ArrayList diferente, sino que cada data dentro también debe ser diferente. De lo contrario, changed no se puede activar.
No se encuentra en proyectos reales, porque los nuevos datos suelen venir de la red.

5 Hoy es el último día del Festival de la Luna Llena, ¡pero mi empresa comenzó a trabajar! Enfadado, escribí un artículo sobre DiffUtil, ¡incluso sin usar DiffUtil, puedo comparar fácilmente las diferencias entre mi empresa y otras empresas! QQQ, y hoy no estaba en buen estado, ¡escribí8Se tardó una hora en completar. Pensé que este artículo podría ser elegido para la colección de microensayos, ¡pero resultó ser bastante largo! Aquellos sin paciencia pueden descargar el DEMO para verlo, no hay mucho código, y es bastante fácil de usar.

Puerta de enlace de github:
https://github.com/mcxtzhang/DiffUtils

Aquí está lo que se dijo sobre Android7Este es el material de investigación de la clase de herramientas DiffUtil .0, se continuará complementando con más materiales relacionados, ¡gracias por el apoyo de todos a este sitio!

Te gustará