Android RecyclerView DiffUtil
在本教程中,我们将在我们的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);
}
}
"价格"是传递给适配器的有效负载数据。
然后,我们可以为字段设置动画/更改其文字颜色,以指示用户已对其进行了修改。

