RecyclerView局部刷新机制——payload
前言
這篇文章其實之前就完成了,一直遺忘在角落里了,今天無意翻之前的筆記發現的,大部分內容應該還是有效的。
之前在使用RecyclerView的遇到過一個問題,使用notifyItemChanged刷新數據的時候會出現重影或者閃爍的現象。
這個問題很容易出現,當我們的列表中有進度顯示(比如下載),這時候需要不停的更新進度,就需要使用notifyItemChanged
使用notifyItemChanged可以只刷新那一個item,這樣就避免了像ListView那樣全部刷新
但是如果使用notifyItemChanged(position),在滑動的時候刷新就會出現重影或者閃爍的問題。
解決這個問題很簡單,將notifyItemChanged(position)替換為notifyItemChanged(position,0)即可。
測試問題確實解決了,但是為啥?這個參數有啥用?
源碼分析
我們從源碼入手來看看
public final void notifyItemChanged(int position, @Nullable Object payload) {this.mObservable.notifyItemRangeChanged(position, 1, payload); }可以看到payload是一個object,并非int。它調用了mObservable的notifyItemRangeChanged
public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {for(int i = this.mObservers.size() - 1; i >= 0; --i) {((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload);}}調用了AdapterDataObserver的onItemRangeChanged,這是一個接口,它的實現是RecyclerViewDataObserver,實現的函數
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {RecyclerView.this.assertNotInLayoutOrScroll((String)null);if(RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {this.triggerUpdateProcessor();} }又調用了mAdapterHelper的onItemRangeChanged
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {if(itemCount < 1) {return false;} else {this.mPendingUpdates.add(this.obtainUpdateOp(4, positionStart, itemCount, payload));this.mExistingUpdateTypes |= 4;return this.mPendingUpdates.size() == 1;} }調用了obtainUpdateOp函數
public AdapterHelper.UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {AdapterHelper.UpdateOp op = (AdapterHelper.UpdateOp)this.mUpdateOpPool.acquire();if(op == null) {op = new AdapterHelper.UpdateOp(cmd, positionStart, itemCount, payload);} else {op.cmd = cmd;op.positionStart = positionStart;op.itemCount = itemCount;op.payload = payload;}return op; }可以看到作為參數賦給一個UpdateOp對象,那么哪里使用了這個對象的payload?
在AdapterHelper中查找發現有幾處這樣的代碼
this.mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);這個callback也是一個接口,在RecyclerView中可以找到它的實現,其中對應的函數:
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {RecyclerView.this.viewRangeUpdate(positionStart, itemCount, payload);RecyclerView.this.mItemsChanged = true; }調用了viewRangeUpdate函數
void viewRangeUpdate(int positionStart, int itemCount, Object payload) {int childCount = this.mChildHelper.getUnfilteredChildCount();int positionEnd = positionStart + itemCount;for(int i = 0; i < childCount; ++i) {View child = this.mChildHelper.getUnfilteredChildAt(i);RecyclerView.ViewHolder holder = getChildViewHolderInt(child);if(holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart && holder.mPosition < positionEnd) {holder.addFlags(2);holder.addChangePayload(payload);((RecyclerView.LayoutParams)child.getLayoutParams()).mInsetsDirty = true;}}this.mRecycler.viewRangeUpdate(positionStart, itemCount); }可以看到調用了holder的addChangePayload
void addChangePayload(Object payload) {if(payload == null) {this.addFlags(1024);} else if((this.mFlags & 1024) == 0) {this.createPayloadsIfNeeded();this.mPayloads.add(payload);}}private void createPayloadsIfNeeded() {if(this.mPayloads == null) {this.mPayloads = new ArrayList();this.mUnmodifiedPayloads = Collections.unmodifiableList(this.mPayloads);}}List<Object> getUnmodifiedPayloads() {return (this.mFlags & 1024) == 0?(this.mPayloads != null && this.mPayloads.size() != 0?this.mUnmodifiedPayloads:FULLUPDATE_PAYLOADS):FULLUPDATE_PAYLOADS; }這里有兩個list,mPayloads和mUnmodifiedPayloads,在getUnmodifiedPayloads中可以看到當mPayloads不為空才會返回mUnmodifiedPayloads,否則返回FULLUPDATE_PAYLOADS,即Collections.EMPTY_LIST。
在RecyclerView中搜索getUnmodifiedPayloads函數,發現其中一處應該跟我們的問題有關
boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {return this.mItemAnimator == null || this.mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads()); }payloads應該對這個函數的返回值有影響,繼續看mItemAnimator的對應函數。
這個mItemAnimator也是一個接口,實現類是DefaultItemAnimator,它的對應函數
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, @NonNull List<Object> payloads) {return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads); }可以看到如果payloads不為空,即最開始的payload不為null(因為是object,所以0還是其它都無所謂,只要不為空就行),canReuseUpdatedViewHolder則為true。
那么canReuseUpdatedViewHolder影響什么,同樣在RecyclerView中搜索發現
void scrapView(View view) {RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(view);if(!holder.hasAnyOfTheFlags(12) && holder.isUpdated() && !RecyclerView.this.canReuseUpdatedViewHolder(holder)) {if(this.mChangedScrap == null) {this.mChangedScrap = new ArrayList();}holder.setScrapContainer(this, true);this.mChangedScrap.add(holder);} else {if(holder.isInvalid() && !holder.isRemoved() && !RecyclerView.this.mAdapter.hasStableIds()) {throw new IllegalArgumentException("Called scrap view with an invalid view. Invalid views cannot be reused from scrap, they should rebound from recycler pool." + RecyclerView.this.exceptionLabel());}holder.setScrapContainer(this, false);this.mAttachedScrap.add(holder);}}可以看到如果有payload,holder會被放入mAttachedScrap,否則放入mChangedScrap。
mAttachedScrap和mChangedScrap
這兩個就涉及到RecyclerView的緩存機制了,整個緩存機制包含多個集合,這兩個集合就是其中的重要部分,這個機制就不在這篇文章里細說了。
先看看它們倆個有什么用
| mAttachedScrap | 未與RecyclerView分離的ViewHolder列表 |
簡單來說當holder有了變化就會放入mChangedScrap,這樣刷新的時候會移除重新bind一下;
而holder沒有改變則放入mAttachedScrap,這樣刷新的時候就不需要重新bind,直接更新數據即可。
所以正是因為沒有payload需要重新bind,所以會出現閃爍。而在滑動中不僅位置一直變,因為進度也在變,所以不停的進行移除bind,就會導致重影的現象。
而使用了payload后,不會移除重新bind,只更新進度條自己,就不會閃爍或重影了。
payload的大用處
最后再補充一個重要的部分!
payload的應用不僅僅是這么簡單,在研究的過程中我還發現了另外一個函數
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) {this.onBindViewHolder(holder, position); }很熟悉吧,這是RecyclerView的Adapter中的一個函數,我們一般使用
public abstract void onBindViewHolder(@NonNull VH var1, int var2);因為上面那個重載的函數不是abstract的,所以我們不容易注意到。那么這個函數有什么用?
可以看到默認處理就是調用了下面的函數,沒什么特殊,但是我們可以重寫它。
比如說我們刷新的時候,只想改變一個TextView的文案
如果是之前的處理,會重新執行一遍onBindViewHolder(@NonNull VH var1, int var2),這樣不僅那個TextView,其它組件也會更新一遍數據,雖然數據沒變,尤其有圖片的時候需要重新load一次。
但是重寫onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads)我們就可以只為TextView重新設置文案即可,如下:
@Override public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {if(payloads.isEmpty()){onBindViewHolder(holder, position);}else{holder.tv.setText("change text");} }而且通過對payload設置不同的值,我們可以通過判斷payload分別處理不同的刷新,比如:
總結
總結
以上是生活随笔為你收集整理的RecyclerView局部刷新机制——payload的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Flutter 中的Error的捕获及处
- 下一篇: 使用SearchView报错java.l