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