Android的RV列表刷新詳解Payload與Diff方式異同
前言
RecyclerView是我們常用的列表控件,一般來說當(dāng)Item的數(shù)據(jù)改變的時候我們需要刷新當(dāng)前的Item 。
如何刷新 RV 的列表?基本上有這幾種方式:
notifyDataSetChanged()
notifyItemChanged(int position)
notifyItemChanged(int position, @Nullable Object payload)
一般來說一個 item 是由多個控件組成的,如 Button、CheckBox、TextView、ImageView、ViewGroup 等組合。當(dāng)我們點擊item的某個控件時,RV 需要重新計算布局、刷新視圖來響應(yīng)交互。假設(shè)一個 item 包含了N多個控件,如果調(diào)用notifyItemChanged(int position) 時,item 中的每個控件都需要重新布局顯示,無形中加大了內(nèi)存和性能的損耗。
就算我們不考慮內(nèi)存和性能的問題,那么一些效果也是我們無法接受的,當(dāng) item 刷新的時候會導(dǎo)致內(nèi)部的圖片或item 出現(xiàn)一次閃爍。
以我的精神糧食起點為例,如下
所以我們才需要用到類似 Payload 與 Diff 之類的刷新方式。
下面我們就一起看看它們是怎么使用的。
一、Payload的刷新
我們通過 notifyItemChanged(int position, @Nullable Object payload)
來刷新指定索引的item。
在 RV 的 Adapter 中的 onBindViewHolder 可以接收到 payloads 參數(shù),這個 payloads 參數(shù)是一個 List 對象,該對象不是 null 但可能是空的。
public void onBindViewHolder(VH holder, int position, List<Object> payloads) { onBindViewHolder(holder, position); }
通過 Adapter 的 notifyXXX 函數(shù)的帶有 payload 參數(shù)的函數(shù)可以設(shè)置 payload 對象,如:
public final void notifyItemChanged(int position, Object payload) { mObservable.notifyItemRangeChanged(position, 1, payload); }
由于 onBindViewHolder 有重載的方法,如果使用了 payloads 的方式,那么我們需要做兼容,如果沒有 payloads 就去走整個 item 的刷新,如果有 payloads 那么我們就根據(jù)指定的 payload 去刷新指定的數(shù)據(jù)。
@Override public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) { if (!payloads.isEmpty() && payloads.get(0).equals("like")) { //如果是我們指定的 payloads ,那么就可以指定刷新 TipsBean item = mData.get(position); holder.tvLikeNum.setText(R.id.tv_tips_like_num, item.likes_count > 0 ? item.likes_count + "" : "Like"); holder.tvLikeNum.setTextColor(R.id.tv_tips_like_num, item.likes_count > 0 ? CommUtils.getColor(R.color.black) : CommUtils.getColor(R.color.home_item_text_light_gray)); } else { // 如果沒有 payloads ,或者不是我們指定的,還是返回默認(rèn)的整個刷新 super.onBindViewHolder(holder, position); } }
如果沒有 payload ,當(dāng)調(diào)用 notifyItemChanged 時,RV 會通過回調(diào) onBindViewHolder(holder, position) 來更新當(dāng)前數(shù)據(jù)變化的 item ,此時會觸發(fā) 整個 item 中 view 的重新布局和計算位置,這樣的話只要是其中一個 View 狀態(tài)變化了,最終會導(dǎo)致整個 item 都需要重新布局一遍。
例如上述的例子,在評論的列表中,我只點贊了第一個 item ,我們就通過 payload 來告訴 RV 這個 item 中的 like 文本變化了,那么我們就只需要處理 Like 文本的變化。
二、Diff的刷新與快速實現(xiàn)方法
每一個 payloads 都要寫一次,然后我們在調(diào)用 notifyItemChanged 時萬一寫錯了怎么辦?有沒有一種方式能讓程序自動管理,讓程序幫我們記錄 payloads ? 最好還能幫助我們自動排序!
有,我們先看看自動排序的方式:SortedList 的方式
SortedList,顧名思義就是排序列表,它適用于列表有序且不重復(fù)的場景。并且SortedList會幫助你比較數(shù)據(jù)的差異,定向刷新數(shù)據(jù)。而不是簡單粗暴的notifyDataSetChanged()。
例如我們定義一個城市排序的對象:
public class City { private int id; private String cityName; private String firstLetter; ... }
我們需要進(jìn)行排序的規(guī)則定義
public class SortedListCallback extends SortedListAdapterCallback<City> { public SortedListCallback(RecyclerView.Adapter adapter) { super(adapter); } /** * 排序條件 */ @Override public int compare(City o1, City o2) { return o1.getFirstLetter().compareTo(o2.getFirstLetter()); } /** * 用來判斷兩個對象是否是相同的Item。 */ @Override public boolean areItemsTheSame(City item1, City item2) { return item1.getId() == item2.getId(); } /** * 用來判斷兩個對象是否是內(nèi)容的Item。 */ @Override public boolean areContentsTheSame(City oldItem, City newItem) { if (oldItem.getId() != newItem.getId()) { return false; } return oldItem.getCityName().equals(newItem.getCityName()); } }
再然后在Adapte中使用的時候,不需要Arrylist,要用排序的集合,新的對象SortedList排序集合。
public class SortedAdapter extends RecyclerView.Adapter<SortedAdapter.ViewHolder> { // 數(shù)據(jù)源使用SortedList private SortedList<City> mSortedList; private LayoutInflater mInflater; public SortedAdapter(Context mContext) { mInflater = LayoutInflater.from(mContext); } public void setSortedList(SortedList<City> mSortedList) { this.mSortedList = mSortedList; } /** * 批量更新操作,例如: * <pre> * mSortedList.beginBatchedUpdates(); * try { * mSortedList.add(item1) * mSortedList.add(item2) * mSortedList.remove(item3) * ... * } finally { * mSortedList.endBatchedUpdates(); * } * </pre> */ public void addData(List<City> mData) { mSortedList.beginBatchedUpdates(); mSortedList.addAll(mData); mSortedList.endBatchedUpdates(); } /** * 移除item */ public void removeData(int index) { mSortedList.removeItemAt(index); } /** * 清除集合 */ public void clear() { mSortedList.clear(); } @Override @NonNull public SortedAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false)); } @Override public void onBindViewHolder(@NonNull SortedAdapter.ViewHolder holder, final int position) { // 。。。 } @Override public int getItemCount() { return mSortedList.size(); } public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View itemView) { super(itemView); } } }
使用的時候:
RecyclerView mRecyclerView = findViewById(R.id.rv); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mSortedAdapter = new SortedAdapter(this); // SortedList初始化 SortedListCallback mSortedListCallback = new SortedListCallback(mSortedAdapter); SortedList mSortedList = new SortedList<>(City.class, mSortedListCallback); mSortedAdapter.setSortedList(mSortedList); mRecyclerView.setAdapter(mSortedAdapter); //添加數(shù)據(jù) ...
這樣確實就能實現(xiàn)自動排序,刷新列表了,相對 notifyDataSetChanged 的暴力刷新,優(yōu)雅一點。但是它沒有 payload 的功能,這個刷新只是刷新的是整個 RV 中的部分Item,但還是刷新整個 item 啊。
有沒有辦法既能排序又能 payload差異化刷新的方式呢?
肯定有哇, DiffUtil 的方式就此誕生,常用的相關(guān)的幾個類為 DiffUtil
AsyncListDiffer
ListAdapter
(用于快速實現(xiàn)的封裝類)
DiffUtil 能實現(xiàn)排序加payload局部刷新的功能:
- 當(dāng)某個 item 的位置變化,觸發(fā)排序邏輯,有移除和添加的動畫。
- 當(dāng)某個 item 的位置不變,內(nèi)容變化,觸發(fā) payload 局部刷新。
- 在子線程中計算DiffResult,在主線程中刷新RecyclerView。
AsyncListDiffer 又是什么東西,為什么需要它。
其實 AsyncListDiffer 就是集成了 AsyncListUtil + DiffUtil 的功能,由于 DiffUtil在計算數(shù)據(jù)差異 DiffUtil.calculateDiff(mDiffCallback)
是一個耗時操作,需要我們放到子線程去處理,最后在主線程刷新,為了我們開發(fā)者更加的方便,谷歌直接提供了 AsyncListDiffer 方便我們直接使用??磥砉雀枋桥挛覀冮_發(fā)者不會使用子線程,直接給我們寫好了。
ListAdapter 又是個什么鬼?怎么越來越復(fù)雜了?
其實谷歌就喜歡把簡單的東西復(fù)雜化,如果我們使用 AsyncListDiffer 去實現(xiàn)的話,雖然不用我們操心子線程了,但是還是需要我們定義對象、集合、添加數(shù)據(jù)的方法 ,如addNewData
,里面調(diào)用 mDiffer.submitList()
才能實現(xiàn)。
谷歌還是怕我們不會用吧!直接把 AsyncListDiffer 的使用都給簡化了,直接提供了 ListAdapter 包裝類,內(nèi)部對 AsyncListDiffer 的使用做了一系列的封裝,使用的時候我們的 RV-Adapter 直接繼承 ListAdapter 即可實現(xiàn) AsyncListDiffer 的功能,內(nèi)部連設(shè)置數(shù)據(jù)的方法都給我們提供好了。谷歌真的是我們的好爸爸!為我們開發(fā)者操碎了心。
那它們都到底怎么使用呢?
不管什么方式,我們都需要定義好自己的 DiffUtil.CallBack,畢竟就算讓程序幫我們排序和差分,我們也得告訴程序排序的規(guī)則和diff的規(guī)則,是吧!
public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback<TestBean> { /** * 是否是同一個對象 */ @Override public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) { return oldItem.getId() == newItem.getId(); } /** * 是否是相同內(nèi)容 */ @Override public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) { return oldItem.getName().equals(newItem.getName()); } /** * areItemsTheSame()返回true而areContentsTheSame()返回false時調(diào)用,也就是說兩個對象代表的數(shù)據(jù)是一條, * 但是內(nèi)容更新了。此方法為定向刷新使用,可選。 */ @Nullable @Override public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) { Bundle payload = new Bundle(); if (!oldItem.getName().equals(newItem.getName())) { payload.putString("KEY_NAME", newItem.getName()); } if (payload.size() == 0){ //如果沒有變化 就傳空 return null; } return payload; } }
那個Diff的具體實現(xiàn)我們選用哪一種方案呢?其實三種方式都是可以實現(xiàn)的,這里我們先使用 AsyncListDiffer 的方式來實現(xiàn)。
上面關(guān)于 AsyncListDiffer 的介紹我們說過了,雖然不需要我們實現(xiàn)異步操作了,但是我們還是需要實現(xiàn)對象、集合、添加數(shù)據(jù)的方法等。
示例如下:
public class AsyncListDifferAdapter extends RecyclerView.Adapter<AsyncListDifferAdapter.ViewHolder> { private LayoutInflater mInflater; // 數(shù)據(jù)的操作由AsyncListDiffer實現(xiàn) private AsyncListDiffer<TestBean> mDiffer; public AsyncListDifferAdapter(Context mContext) { // 初始化AsyncListDiffe mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback()); mInflater = LayoutInflater.from(mContext); } //添加數(shù)據(jù)傳對象和對象集合都可以 public void addData(TestBean mData){ List<TestBean> mList = new ArrayList<>(); mList.addAll(mDiffer.getCurrentList()); mList.add(mData); mDiffer.submitList(mList); } public void addData(List<TestBean> mData){ // 由于DiffUtil是對比新舊數(shù)據(jù),所以需要創(chuàng)建新的集合來存放新數(shù)據(jù)。 // 實際情況下,每次都是重新獲取的新數(shù)據(jù),所以無需這步。 List<TestBean> mList = new ArrayList<>(); mList.addAll(mData); mDiffer.submitList(mList); } //刪除數(shù)據(jù),要先獲取全部集合,再刪除指定的集合,再提交刪除之后的集合 public void removeData(int index){ List<TestBean> mList = new ArrayList<>(); mList.addAll(mDiffer.getCurrentList()); mList.remove(index); mDiffer.submitList(mList); } public void clear(){ mDiffer.submitList(null); } @Override @NonNull public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); } else { Bundle bundle = (Bundle) payloads.get(0); holder.mTvName.setText(bundle.getString("KEY_NAME")); } } @Override public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) { TestBean bean = mDiffer.getCurrentList().get(position); holder.mTvName.setText(bean.getName()); } @Override public int getItemCount() { return mDiffer.getCurrentList().size(); } static class ViewHolder extends RecyclerView.ViewHolder { ...... } }
由于 Diff.Callback 我們已經(jīng)在 Adapter 內(nèi)部已經(jīng)初始化了,所以使用的時候我們直接像普通的 RV 設(shè)置 Adapter 一樣即可。
在更新數(shù)據(jù)的時候我們使用 Adapter 定義的 addData
和 removeData
即可完成Diff刷新。
使用 ListAdapter 會怎樣?
如果覺得使用 AsyncListDiffer 都嫌棄麻煩的話,我們直接使用 ListAdapter 也能實現(xiàn)。
由于我們還是需要一個List集合去保存我們的數(shù)據(jù),我們就能對 ListAdapter 再做一個簡單的基類封裝。
public abstract class BaseRVDifferAdapter<T, VH extends RecyclerView.ViewHolder> extends ListAdapter<T, VH> { protected Context mContext; protected LayoutInflater mInflater; protected List<T> mDatas = new ArrayList<>(); public BaseRVDifferAdapter(Context context, DiffUtil.ItemCallback<T> callback) { super(callback); mContext = context; mInflater = LayoutInflater.from(mContext); } //設(shè)置數(shù)據(jù)源 protected void setData(List<T> list) { mDatas.clear(); mDatas.addAll(list); List<T> mList = new ArrayList<>(); mList.addAll(mDatas); submitList(mList); } //添加數(shù)據(jù)源 protected void addData(List<T> list) { mDatas.addAll(list); List<T> mList = new ArrayList<>(); mList.addAll(mDatas); submitList(mList); } //刪除制定索引數(shù)據(jù)源 protected void removeData(int index) { mDatas.remove(index); List<T> mList = new ArrayList<>(); mList.addAll(mDatas); submitList(mList); } //清除全部數(shù)據(jù) protected void clear() { mDatas.clear(); submitList(null); } //獲取adapter維護(hù)的數(shù)據(jù)集合 protected List<T> getAdapterData() { return mDatas; } }
我們實現(xiàn)Adater的方法就如下:
public class MyListAdapter extends BaseRVDifferAdapter<TestBean, MyListAdapter.ViewHolder> { public MyListAdapter(Context context) { super(context, new MyDiffUtilItemCallback()); } @Override @NonNull public MyListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) { if (payloads.isEmpty()) { onBindViewHolder(holder, position); } else { Bundle bundle = (Bundle) payloads.get(0); holder.mTvName.setText(bundle.getString("KEY_NAME")); } } @Override public void onBindViewHolder(@NonNull MyListAdapter.ViewHolder holder, final int position) { TestBean bean = getItem(position); holder.mTvName.setText(bean.getName()); } static class ViewHolder extends RecyclerView.ViewHolder { TextView mTvName; ViewHolder(View itemView) { super(itemView); mTvName = itemView.findViewById(R.id.tv_name); } } }
在我們自己的 Adpater 中,我們還是需要初始化我們自己的 Diff.Callback的。那么在使用的時候也就和普通的RV設(shè)置 Adapter 是一樣的。
如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_sorted_list); RecyclerView mRecyclerView = findViewById(R.id.rv); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mAsyncListDifferAdapter = new AsyncListDifferAdapter(this); mRecyclerView.setAdapter(mAsyncListDifferAdapter); initData(); } private void addData() { List<TestBean> mList = new ArrayList(); for (int i = 10; i < 20; i++){ mList.add(new TestBean(i, "Item " + i)); } mMyListAdapter.addData(mList); } private void initData() { List<TestBean> mList = new ArrayList(); for (int i = 0; i < 10; i++){ mList.add(new TestBean(i, "Item " + i)); } mMyListAdapter.setData(mList); } private void updateData() { List<TestBean> mList = new ArrayList(); for (int i = 20; i < 30; i++){ mList.add(new TestBean(i, "Item " + i)); } mMyListAdapter.addData(mList); }
是不是可以無腦使用Diff的方式呢?也不是,當(dāng)一些普通長列表我們使用默認(rèn)的RV-Adapter就行了,加載更多,往源數(shù)據(jù)上面加數(shù)據(jù),并不涉及到很多數(shù)據(jù)源改變的也沒必要使用Diff。
那么哪里使用?比如IM的會話列表,長期變動的數(shù)據(jù),比如評論區(qū)域熱評區(qū)的數(shù)據(jù)切換時,比如我們的Android掘金App:
直接暴力的切換數(shù)據(jù)源然后 notifyDataSetChanged ,這樣就會整個頁面閃爍。而對于這些特定的一些場景,我們使用 Diff 的功能,就會感覺更加的流暢,體驗會好一點哦!
三、DiffUtil的封裝
既然 AsyncListDiffer 和 ListAdapter 都是快速實現(xiàn)的方式,那我們直接使用基本 DiffUtil 行不行?
當(dāng)然可以,我不想直接使用 ListAdapter ,我就想用 RV.Adapter ,因為我有其他的封裝與擴(kuò)展,我自己會使用異步線程,我不需要你幫我管理,我不需要使用你的 AsyncListDiffer 幫我管理我的 List 對象 。
我們可以直接使用基本的 DiffUtil 。真實使用下來也不是很復(fù)雜,只需要做一點點的封裝也能很方便的實現(xiàn)邏輯。
先寫一個Differ的配置文件,內(nèi)部可配置主線程,異步線程,和必備的DiffUtil.Callback:
class MyAsyncDifferConfig <T>( @SuppressLint("SupportAnnotationUsage") @RestrictTo(RestrictTo.Scope.LIBRARY) val mainThreadExecutor: Executor?, val backgroundThreadExecutor: Executor, val diffCallback: DiffUtil.ItemCallback<T>) { class Builder<T>(private val mDiffCallback: DiffUtil.ItemCallback<T>) { companion object { private val sExecutorLock = Any() private var sDiffExecutor: Executor? = null } private var mMainThreadExecutor: Executor? = null private var mBackgroundThreadExecutor: Executor? = null fun setMainThreadExecutor(executor: Executor?): Builder<T> { mMainThreadExecutor = executor return this } fun setBackgroundThreadExecutor(executor: Executor?): Builder<T> { mBackgroundThreadExecutor = executor return this } fun build(): MyAsyncDifferConfig<T> { if (mBackgroundThreadExecutor == null) { synchronized(sExecutorLock) { if (sDiffExecutor == null) { sDiffExecutor = Executors.newSingleThreadExecutor() } } mBackgroundThreadExecutor = sDiffExecutor } return MyAsyncDifferConfig( mMainThreadExecutor, mBackgroundThreadExecutor!!, mDiffCallback) } } }
重點是定義一個自己的AsyncDiffer,內(nèi)部使用 DiffUtil 來計算差分。
class MyAsyncDiffer<T>( private val adapter: RecyclerView.Adapter<*>, private val config: MyAsyncDifferConfig<T> ) { private val mUpdateCallback: ListUpdateCallback = AdapterListUpdateCallback(adapter) private var mMainThreadExecutor: Executor = config.mainThreadExecutor ?: MainThreadExecutor() private val mListeners: MutableList<ListChangeListener<T>> = CopyOnWriteArrayList() private var mMaxScheduledGeneration = 0 private var mList: List<T>? = null private var mReadOnlyList = emptyList<T>() private class MainThreadExecutor internal constructor() : Executor { val mHandler = Handler(Looper.getMainLooper()) override fun execute(command: Runnable) { mHandler.post(command) } } fun getCurrentList(): List<T> { return mReadOnlyList } @JvmOverloads fun submitList(newList: MutableList<T>?, commitCallback: Runnable? = null) { val runGeneration: Int = ++mMaxScheduledGeneration if (newList == mList) { commitCallback?.run() return } val previousList = mReadOnlyList if (newList == null) { val countRemoved = mList?.size ?: 0 mList = null mReadOnlyList = emptyList() mUpdateCallback.onRemoved(0, countRemoved) onCurrentListChanged(previousList, commitCallback) return } if (mList == null) { mList = newList mReadOnlyList = Collections.unmodifiableList(newList) mUpdateCallback.onInserted(0, newList.size) onCurrentListChanged(previousList, commitCallback) return } val oldList: List<T> = mList as List<T> config.backgroundThreadExecutor.execute { val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize(): Int { return oldList.size } override fun getNewListSize(): Int { return newList.size } override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] return if (oldItem != null && newItem != null) { config.diffCallback.areItemsTheSame(oldItem, newItem) } else oldItem == null && newItem == null } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] if (oldItem != null && newItem != null) { return config.diffCallback.areContentsTheSame(oldItem, newItem) } if (oldItem == null && newItem == null) { return true } throw AssertionError() } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem: T? = oldList[oldItemPosition] val newItem: T? = newList[newItemPosition] if (oldItem != null && newItem != null) { return config.diffCallback.getChangePayload(oldItem, newItem) } throw AssertionError() } }) mMainThreadExecutor.execute { if (mMaxScheduledGeneration == runGeneration) { latchList(newList, result, commitCallback) } } } } private fun latchList( newList: MutableList<T>, diffResult: DiffUtil.DiffResult, commitCallback: Runnable? ) { val previousList = mReadOnlyList mList = newList mReadOnlyList = Collections.unmodifiableList(newList) diffResult.dispatchUpdatesTo(mUpdateCallback) onCurrentListChanged(previousList, commitCallback) } private fun onCurrentListChanged( previousList: List<T>, commitCallback: Runnable? ) { for (listener in mListeners) { listener.onCurrentListChanged(previousList, mReadOnlyList) } commitCallback?.run() } //定義接口 interface ListChangeListener<T> { fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) } fun addListListener(listener: ListChangeListener<T>) { mListeners.add(listener) } fun removeListListener(listener: ListChangeListener<T>) { mListeners.remove(listener) } fun clearAllListListener() { mListeners.clear() } }
然后我們可以用委托的方式配置,可以讓普通的 RecyclerView.Adapter 也能通過配置的方式選擇是否使用Differ。
實現(xiàn)我們的控制類接口
//設(shè)置別名簡化 typealias IDiffer<T> = IMyDifferController<T> fun <T> differ(): MyDifferController<T> = MyDifferController() interface IMyDifferController<T> { fun RecyclerView.Adapter<*>.initDiffer(config: MyAsyncDifferConfig<T>): MyAsyncDiffer<T> fun getDiffer(): MyAsyncDiffer<T>? fun getCurrentList(): List<T> fun setDiffNewData(list: MutableList<T>, commitCallback: Runnable? = null) fun addDiffNewData(list: MutableList<T>, commitCallback: Runnable? = null) fun addDiffNewData(t: T, commitCallback: Runnable? = null) fun removeDiffData(index: Int) fun clearDiffData() fun RecyclerView.Adapter<*>.onCurrentListChanged(previousList: List<T>, currentList: List<T>) }
在對控制類接口實例化,做一些具體的操作邏輯
class MyDifferController<T> : IMyDifferController<T> { private var mDiffer: MyAsyncDiffer<T>? = null override fun RecyclerView.Adapter<*>.initDiffer(config: MyAsyncDifferConfig<T>): MyAsyncDiffer<T> { mDiffer = MyAsyncDiffer(this, config) val mListener: MyAsyncDiffer.ListChangeListener<T> = object : MyAsyncDiffer.ListChangeListener<T> { override fun onCurrentListChanged(previousList: List<T>, currentList: List<T>) { this@initDiffer.onCurrentListChanged(previousList, currentList) } } mDiffer?.addListListener(mListener) return mDiffer!! } override fun getDiffer(): MyAsyncDiffer<T>? { return mDiffer } override fun getCurrentList(): List<T> { return mDiffer?.getCurrentList() ?: emptyList() } override fun setDiffNewData(list: MutableList<T>, commitCallback: Runnable?) { mDiffer?.submitList(list, commitCallback) } override fun addDiffNewData(list: MutableList<T>, commitCallback: Runnable?) { val newList = mutableListOf<T>() newList.addAll(mDiffer?.getCurrentList() ?: emptyList()) newList.addAll(list) mDiffer?.submitList(newList, commitCallback) } override fun addDiffNewData(t: T, commitCallback: Runnable?) { val newList = mutableListOf<T>() newList.addAll(mDiffer?.getCurrentList() ?: emptyList()) newList.add(t) mDiffer?.submitList(newList, commitCallback) } override fun removeDiffData(index: Int) { val newList = mutableListOf<T>() newList.addAll(mDiffer?.getCurrentList() ?: emptyList()) newList.removeAt(index) mDiffer?.submitList(newList) } override fun clearDiffData() { mDiffer?.submitList(null) } override fun RecyclerView.Adapter<*>.onCurrentListChanged(previousList: List<T>, currentList: List<T>) { } }
到此我們就能封裝一個DiffUtil的工具類了,我們可以選擇是否啟用Diff,例如我們不使用Diff,我們使用Adapter就是一個普通的Adaper
class MyDiffAdapter() : RecyclerView.Adapter<BaseViewHolder>() { private val mDatas = arrayListOf<DemoDiffBean>() fun addData(list :List<DemoDiffBean>) { mDatas.addAll(list) notifyDataSetChanged() } override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) { if (!CheckUtil.isEmpty(payloads) && (payloads[0] as String) == "text") { YYLogUtils.w("差分刷新 -------- 文本更新") holder.setText(R.id.tv_job_text, mDatas[position].content) } else { onBindViewHolder(holder, position) } } override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { YYLogUtils.w("默認(rèn)數(shù)據(jù)賦值 --------") holder.setText(R.id.tv_job_text, mDatas[position].content) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { return BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent, false)) } override fun getItemCount(): Int { return mDatas.size } }
如果我們想啟動Diff的功能的時候,實現(xiàn)這個接口并委托實現(xiàn)即可啟用Diff。
class MyDiffAdapter() : RecyclerView.Adapter<BaseViewHolder>(), IDiffer<DemoDiffBean> by differ() { init { initDiffer(MyAsyncDifferConfig.Builder(DiffDemoCallback()).build()) } override fun onBindViewHolder(holder: BaseViewHolder, position: Int, payloads: MutableList<Any>) { if (!CheckUtil.isEmpty(payloads) && (payloads[0] as String) == "text") { YYLogUtils.w("差分刷新 -------- 文本更新") holder.setText(R.id.tv_job_text, getCurrentList()[position].content) } else { onBindViewHolder(holder, position) } } override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { YYLogUtils.w("默認(rèn)數(shù)據(jù)賦值 --------") holder.setText(R.id.tv_job_text, getCurrentList()[position].content) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { return BaseViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_diff_jobs, parent, false)) } override fun getItemCount(): Int { return getCurrentList().size } }
使用的時候也是超方便的
private fun initRV() { mAdapter = MyDiffAdapter() findViewById<RecyclerView>(R.id.recyclerView).vertical().apply { adapter = mAdapter divider(Color.BLACK) } } private fun initData() { mDatas.clear() for (i in 1..10) { mDatas.add(DemoDiffBean(i, "conetnt:$i")) } mAdapter.setDiffNewData(mDatas) } private fun initListener() { findViewById<View>(R.id.diff_1).click { val list = mutableListOf<DemoDiffBean>() for (i in 1..10) { list.add(DemoDiffBean(i, "Diff1 conetnt:$i")) } mAdapter.setDiffNewData(list) } findViewById<View>(R.id.diff_2).click { val list = mutableListOf<DemoDiffBean>() for (i in 1..10) { list.add(DemoDiffBean(i, "Diff3 conetnt:$i")) } list.removeAt(0) list.removeAt(1) list.removeAt(2) list[3].content = "自定義亂改的數(shù)據(jù)" mAdapter.setDiffNewData(list) } }
運行的效果如下:
是不是很方便呢?可能有人會問,這么封裝有什么好處?
其實這樣通過配置的方式,我們可以使用在任意的RV.Adapter上面,包括我們自己定義的BaseAdapter,LoadMoreAdapter等。相對比較靈活吧,方便在原有的效果上快速修改。
當(dāng)然了,其實關(guān)于 Diff 的實現(xiàn),有這么多種方式可以讓大家使用,每一種方案都能實現(xiàn)同樣的效果,只是看大家愿不愿意優(yōu)化而已,每一種方式使用起來都不算難。
小結(jié)
關(guān)于 paylpoad 和 Diff 的問題,既然 Diff 是在 payload 的基礎(chǔ)上實現(xiàn)的,那是不是有 Diff 功能之后我們就不需要手動 payload 了呢?
也不是,上面的介紹中已經(jīng)講過了,如果數(shù)據(jù)頻繁的切換,最好是使用Diff,如果就是類似普通的評論列表點贊的效果,我們手動 payload 即可。他們有各自的使用場景。
需要注意的是,如果使用 Diff 要留意對象指針的問題,DiffUtil 首先檢查新提交的 newList 與內(nèi)部持有的 mList 的引用是否相同, 如果相同, 就直接返回。如果不同的引用,才會對 newList 和 mList 做 Diff 算法比較。
可以看的我的 Demo 都是直接另 new 一個 List 來進(jìn)行操作的,正常開發(fā)場景一般我們都是從服務(wù)器拿的不同的 List,也不會有問題。而如果從本地拿的數(shù)據(jù)去比對的時候就需要注意,對比的對象和原有的對象是否是同一個對象,此時可以考慮對象的深拷貝來實現(xiàn)新對象,再拿去和原有對象進(jìn)行差分對比,關(guān)于淺拷貝與深拷貝的對比和如何深拷貝,可以看我之前的文章【傳送門】。
好了,本文的全部代碼與Demo都已經(jīng)開源。有興趣可以看這里。項目會持續(xù)更新,大家可以關(guān)注一下。
以上就是Android的RV列表刷新詳解Payload與Diff方式異同的詳細(xì)內(nèi)容,更多關(guān)于Android RV列表刷新Payload Diff的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中實現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度
本篇文章主要介紹了Android中實現(xiàn)OkHttp上傳文件到服務(wù)器并帶進(jìn)度,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07Android制作登錄頁面并且記住賬號密碼功能的實現(xiàn)代碼
這篇文章主要介紹了Android制作登錄頁面并且記住賬號密碼功能的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Android?手寫RecyclerView實現(xiàn)列表加載
這篇文章主要介紹了Android?手寫RecyclerView實現(xiàn)列表加載,涉及到列表的需求,肯定第一時間想到RecyclerView,即便是自定義View,那么RecyclerView也會是首選,為什么會選擇RecyclerView而不是ListView,主要就是RecyclerView的內(nèi)存復(fù)用機制,這也是RecyclerView的核心?2022-08-08