Android RecyclerView DiffUtil

时间:2020-02-23 14:29:11  来源:igfitidea点击:

在本教程中,我们将在我们的android应用程序中讨论和实现DiffUtil。
如果您尚未实现RecyclerView,请先参考本教程,然后再继续。

Android RecyclerView DiffUtil

DiffUtil是Android支持库中提供的实用程序类。
这是一个回调,可检测两个列表之间的更改。
在我们的RecyclerView适配器中,它被用作notifyDataSetChanged()的替代方法。

notifyDataSetChanged非常昂贵。
无论何时调用,它都会更新每个RecyclerView行。
这是昂贵的。
DiffUtil不会调用每一行。
相反,它仅更新值列表之间不同的那些行。

DiffUtil是一个实用程序类,可以计算两个列表之间的差,并输出一个将第一个列表转换为第二个列表的操作列表。

DiffUtil使用RecyclerViewAdapter的以下方法:

  • notifyItemMoved()
  • notifyItemRangeChanged()
  • notifyItemRangeInserted()
  • notifyItemRangeRemoved()

由于它们可用于单个操作,因此它们的成本要低于" notifyDataSetChanged"。

DiffUtil.CallBack类具有以下需要实现的方法:

  • getOldListSize():返回旧列表的大小。

  • getNewListSize():返回新列表的大小。

  • AreItemsTheSame(int oldItemPosition,int newItemPosition):我们检查列表中的各个项目是否相同。
    这可以通过检查其ID来完成。

  • areContentsTheSame(int oldItemPosition,int newItemPosition):检查List数据的内容是否相同。
    仅当areItemsTheSame返回true时,DiffUtil才会调用此方法。

  • getChangePayload(int oldItemPosition,int newItemPosition):如果areItemTheSame返回true,areContentsTheSame返回false DiffUtil调用此方法以获取有关更改的有效负载。
    其中我们可以检测数据的任何特定字段是否更改。
    然后,我们可以使用Bundle传递更改后的值。
    它会在我们的RecyclerView Adapter类中接收。

足够的理论,让我们现在通过示例进行学习。

在以下部分中,我们将创建一个应用程序,该应用程序在RecyclerView中显示CryptoCurrency硬币的虚拟数据。
使用两个FloatingActionButtons,我们可以更改数据(添加更多数据,修改当前数据)。

代码

下面给出了activity_main.xml布局的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.v7.widget.RecyclerView
      android:id="@+id/recyclerView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" 

  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fabAddList"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      android:layout_margin="16dp"
      android:src="@android:drawable/ic_input_add" 

  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fabChangeList"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_margin="16dp"
      android:src="@android:drawable/ic_menu_sort_by_size" 

</RelativeLayout>

下面给出了cardview_item_layout.xml文件的代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:orientation="vertical"
      android:padding="8dp">

      <TextView
          android:id="@+id/txtName"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Bitcoin" 

      <TextView
          android:id="@+id/txtPrice"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="1000 USD" 

  </LinearLayout>

</android.support.v7.widget.CardView>

这将承载RecyclerView行。

让我们看一下我们的Model.java类:

package com.theitroad.androidrecyclerviewdiffutil;

public class Model implements Comparable, Cloneable {

  public String name;
  public int id, price;

  public Model(int id, String name, int price) {
      this.id = id;
      this.name = name;
      this.price = price;
  }

  @Override
  public int compareTo(Object o) {
      Model compare = (Model) o;

      if (compare.id == this.id && compare.name.equals(this.name) && compare.price == (this.price)) {
          return 0;
      }
      return 1;
  }

  @Override
  public Model clone() {

      Model clone;
      try {
          clone = (Model) super.clone();

      } catch (CloneNotSupportedException e) {
          throw new RuntimeException(e); //should not happen
      }

      return clone;
  }

}

我们已经实现了可比和克隆接口。

它们将用于比较DiffUtil中的对象,并创建模型ArrayList的深层副本,我们将在后面看到。

下面给出了MainActivity.java类的代码:

package com.theitroad.androidrecyclerviewdiffutil;

import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

  RecyclerView recyclerView;
  RecyclerViewAdapter recyclerViewAdapter;
  FloatingActionButton fabAddList, fabChangeList;

  private ArrayList<Model> modelArrayList = new ArrayList<>();

  public int i = 1;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      recyclerView = findViewById(R.id.recyclerView);
      fabAddList = findViewById(R.id.fabAddList);
      fabChangeList = findViewById(R.id.fabChangeList);

      dummyData();
      recyclerViewAdapter = new RecyclerViewAdapter(modelArrayList);
      recyclerView.setLayoutManager(new LinearLayoutManager(this));
      recyclerView.setItemAnimator(new DefaultItemAnimator());
      recyclerView.setAdapter(recyclerViewAdapter);

      fabAddList.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              addMoreCoinsToTheList();
          }
      });

      fabChangeList.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              changePricesInTheList();
          }
      });
      
  }

  private void dummyData() {

      modelArrayList.add(new Model(i++, "Bitcoin", 8000));
      modelArrayList.add(new Model(i++, "Ethereum", 600));
      modelArrayList.add(new Model(i++, "Litecoin", 250));
      modelArrayList.add(new Model(i++, "Bitcoin Cash", 1000));
  }

  private void addMoreCoinsToTheList() {
      ArrayList<Model> models = new ArrayList<>();

      for (Model model : modelArrayList) {
          models.add(model.clone());
      }
      models.add(new Model(i++, "Tron", 1));
      models.add(new Model(i++, "Ripple", 5));
      models.add(new Model(i++, "NEO", 100));
      models.add(new Model(i++, "OMG", 20));

      recyclerViewAdapter.setData(models);
  }

  private void changePricesInTheList() {

      ArrayList<Model> models = new ArrayList<>();
      
      for (Model model : modelArrayList) {
          models.add(model.clone());
      }

      for (Model model : models) {
          if (model.price < 900)
              model.price = 900;
      }
      recyclerViewAdapter.setData(models);
  }

}

ArrayList通过引用传递。
我们需要创建ArrayList的深层副本,以防止在修改新的ArrayList时更改原始ArrayList。
否则,DiffUtil将获得两个相同的ArrayList。

recyclerViewAdapter.setData(models);用于在Adapter类中设置新数据。

下面给出了RecyclerViewAdapter.java类的代码:

package com.theitroad.androidrecyclerviewdiffutil;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.CryptoViewHolder> {

  private ArrayList<Model> data;

  public class CryptoViewHolder extends RecyclerView.ViewHolder {

      private TextView mName, mPrice;

      public CryptoViewHolder(View itemView) {
          super(itemView);
          mName = itemView.findViewById(R.id.txtName);
          mPrice = itemView.findViewById(R.id.txtPrice);
      }
  }

  public RecyclerViewAdapter(ArrayList<Model> data) {
      this.data = data;
  }

  @Override
  public CryptoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_item_layout, parent, false);
      return new CryptoViewHolder(itemView);
  }

  @Override
  public void onBindViewHolder(CryptoViewHolder holder, int position) {
      holder.mName.setText(data.get(position).name);
      holder.mPrice.setText(data.get(position).price + " USD");
  }

  @Override
  public void onBindViewHolder(CryptoViewHolder holder, int position, List<Object> payloads) {

      if (payloads.isEmpty()) {
          super.onBindViewHolder(holder, position, payloads);
      } else {
          Bundle o = (Bundle) payloads.get(0);
          for (String key : o.keySet()) {
              if (key.equals("price")) {
                  holder.mName.setText(data.get(position).name);
                  holder.mPrice.setText(data.get(position).price + " USD");
                  holder.mPrice.setTextColor(Color.GREEN);
              }
          }
      }
  }

  @Override
  public int getItemCount() {
      return data.size();
  }

  public ArrayList<Model> getData() {
      return data;
  }

  public void setData(ArrayList<Model> newData) {

      DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new MyDiffUtilCallBack(newData, data));
      diffResult.dispatchUpdatesTo(this);
      data.clear();
      this.data.addAll(newData);
  }
}

当DiffUtil在适配器中进行更改时,会触发onBindViewHolder(CryptoViewHolder持有人,int位置,List <Object>有效负载)。

其中,我们从DiffUtil类的有效负载中获得bundle值。
这包含已更改的各个字段。

在setData()内部,我们传递了新的ArrayList。

dispatchUpdatesTo(this)调用适配器并通知其有关要更新的视图。

接下来,我们将" newData"列表复制到" data"。

下面给出了MyDiffUtilCallback.java类的代码:

package com.theitroad.androidrecyclerviewdiffutil;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.util.DiffUtil;
import java.util.ArrayList;

public class MyDiffUtilCallBack extends DiffUtil.Callback {
  ArrayList<Model> newList;
  ArrayList<Model> oldList;

  public MyDiffUtilCallBack(ArrayList<Model> newList, ArrayList<Model> oldList) {
      this.newList = newList;
      this.oldList = oldList;
  }

  @Override
  public int getOldListSize() {
      return oldList != null ? oldList.size() : 0;
  }

  @Override
  public int getNewListSize() {
      return newList != null ? newList.size() : 0;
  }

  @Override
  public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
      return newList.get(newItemPosition).id == oldList.get(oldItemPosition).id;
  }

  @Override
  public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
      int result = newList.get(newItemPosition).compareTo(oldList.get(oldItemPosition));
      return result == 0;
  }

  @Nullable
  @Override
  public Object getChangePayload(int oldItemPosition, int newItemPosition) {
      
      Model newModel = newList.get(newItemPosition);
      Model oldModel = oldList.get(oldItemPosition);

      Bundle diff = new Bundle();

      if (newModel.price != (oldModel.price)) {
          diff.putInt("price", newModel.price);
      }
      if (diff.size() == 0) {
          return null;
      }
      return diff;
      //return super.getChangePayload(oldItemPosition, newItemPosition);
  }
}

"价格"是传递给适配器的有效负载数据。

然后,我们可以为字段设置动画/更改其文字颜色,以指示用户已对其进行了修改。