??熟悉RecyclerView的同學(xué)應(yīng)該都知道,Adapter作為RecyclerView四大組成部分(Adapter,LayoutManager,ItemAnimator,ItemDecoration)之一,其重要性自然是不言而喻。今天,我們來分析一下Adapter的源碼。我打算將Adapter的源碼分析分為兩個部分,一是,從普通的角度上來看Adapter,從源碼的角度上來分析我們?nèi)粘J褂玫囊恍┎僮?;二是,分?code>DiffUtil,可能會涉及到Adapter的部分源碼。所以Adapter源碼分析分為兩篇,本文是第一篇。
1. 概述
??在分析Adapter源碼之前,我們先來回顧一下,我們經(jīng)常使用的幾個方法。
| 方法名 | 作用 |
|---|---|
| onCreateViewHolder | 創(chuàng)建一個ViewHolder對象,主要作用是將數(shù)據(jù)保存在ViewHolder,以供后面bind操作使用 |
| onBindViewHolder | 數(shù)據(jù)綁定方法 |
| getItemCount | 當(dāng)前Adapter擁有數(shù)據(jù)的數(shù)量,該方法必須被重寫,否則RecyclerView展示不了任何數(shù)據(jù) |
| getItemViewType | 該方法帶一個Position,主要是返回當(dāng)前位置的ViewType。這個方法通常用于一個RecyclerView需要加載不同的布局。 |
| getItemId | 該方法表示的意思是返回當(dāng)前位置Item的id,此方法只在setHasStableIds設(shè)置為true才會生效 |
| setHasStableIds | 設(shè)置當(dāng)前RecyclerView的ItemView是否擁有固定id,跟getItemId方法一起使用。如果設(shè)置為true,會提高RecyclerView的緩存效率。 |
??上表中所列的方法應(yīng)該就是我們使用Adapter經(jīng)常使用的方法,接下來,我將正式分析Adapter的相關(guān)代碼。我打算從如下角度來分析:
- 重新從
RecyclerView緩存角度來分析onCreateViewHolder和onBindViewHolder。onBindViewHolder的一個重載方法--主要是用于局部刷新。- 結(jié)合
Adapter,分析ViewHolder的position。
1. onCreateViewHolder和onBindViewHolder
??onCreateViewHolder方法和onBindViewHolder方法算是我們使用次數(shù)最多的方法,很多自定義Adapter的框架也都是從這兩個方法入手的。我們來看看這兩個方法到底有什么作用。
(1).onCreateViewHolder
??首先,我們來看一下onCreateViewHolder方法,從它的調(diào)用時機入手。
??在本文之前,我分析過RecyclerView的緩存機制,當(dāng)時我將RecyclerView的緩存分為4級緩存,其中分別是:
- 一級緩存:
scrap數(shù)組- 二級緩存:
CachedView- 三級緩存:
ViewCacheExtension- 四級緩存:
RecyclerViewPool
??LayoutManager會獲取ViewHolder時,如果4級緩存都沒有命中,就會調(diào)用Adapter的onCreateViewHolder方法來創(chuàng)建一個新的ViewHolder。我們來看看相關(guān)的代碼:
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
??上面的代碼是RecyclerView的tryGetViewHolderForPositionByDeadline方法代碼片段。之前,我們在分析緩存機制時,就已經(jīng)仔細分析這個方法,這里我就不再贅述,有興趣的同學(xué)可以我之前的文章:RecyclerView 源碼分析(三) - RecyclerView的緩存機制。
??我們回到上面的代碼片段中來,從上面的代碼上,我們看到這里是調(diào)用的是Adapter的createViewHolder方法來創(chuàng)建ViewHolder。我們來看看Adapter的createViewHolder方法:
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
try {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
if (holder.itemView.getParent() != null) {
throw new IllegalStateException("ViewHolder views must not be attached when"
+ " created. Ensure that you are not passing 'true' to the attachToRoot"
+ " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
}
holder.mItemViewType = viewType;
return holder;
} finally {
TraceCompat.endSection();
}
}
??其實createViewHolder方法里面也沒有做什么的操作,差不多就是調(diào)用onCreateViewHolder方法。簡而言之,onCreateViewHolder有點帶兜底的韻味,緩存都沒有命中,只能乖乖的創(chuàng)建ViewHolder。
??我們來看看第二方法,也就是onBindViewHolder方法。
(2). onBindViewHolder
??我們都知道,onBindViewHolder方法的作用是進行數(shù)據(jù)綁定,所以執(zhí)行這個方法的條件相對于onCreateViewHolder有點苛刻。為什么呢?我們這么想一下吧,假設(shè)我們change了其中一個ItemView的數(shù)據(jù),然后通過notifyItemChanged來通知數(shù)據(jù)源已經(jīng)改變。在這種情況下,正常來說,都是只刷新對應(yīng)位置的ItemView就行了,沒必要刷新其他數(shù)據(jù)沒有改變的ItemView(這里的刷新就是指執(zhí)行onBindViewHolder方法)?,F(xiàn)在,我們來看看對應(yīng)的執(zhí)行代碼:
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
??從上面的代碼,我們可以看出來,最后調(diào)用了tryBindViewHolderByDeadline方法。而調(diào)用tryBindViewHolderByDeadline方法條件比較苛刻,不過不管怎么苛刻,只要記住一點,如果對應(yīng)位置的數(shù)據(jù)被更新了,該位置會執(zhí)行一次onBindViewHolder方法。我們繼續(xù)看一下tryBindViewHolderByDeadline方法的代碼:
private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
// ······
mAdapter.bindViewHolder(holder, offsetPosition);
// ······
}
??執(zhí)行過程跟onCreateViewHolder方法差不多,都是在依靠Adapter內(nèi)部一個對應(yīng)的final方法來回調(diào)。這樣所做的好處,可以在onBindViewHolder方法執(zhí)行前后做一些其他的操作,比如初始化操作和清理操作,這種模式有點類似于Java中靜態(tài)代理模式中的繼承代理。然后,我們來看看Adapter的bindViewHolder方法:
public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
??從這里,我們可以簡單發(fā)現(xiàn),在執(zhí)行onBindViewHolder方法前后,各自做了一些不同的操作。比如,在執(zhí)行onBindViewHolder方法之前,更新了ViewHolder的mPosition屬性和給ViewHolder設(shè)置了一些flag;在執(zhí)行onBindViewHolder方法之后,清理了ViewHolder的payload,并且還是給ItemView的LayoutParams的mInsetsDirty屬性設(shè)置為true。
??這里額外的提出兩個點:
payload主要是用于局部刷新的,待會我會詳細解釋怎么進行局部刷新。- 關(guān)于
LayoutParams的mInsetsDirty屬性,這個屬性尤為重要的,主要用于ItemView的繪制,后續(xù)我在分析ItemDecoration時會詳細的解釋這個屬性。
3. 局部刷新的基本使用和實現(xiàn)原理
(1). 局部刷新的基本使用
??在分析局部刷新之前,我們先來討論一下怎么進行布局刷新,也就是說怎么通過RecyclerView實現(xiàn)ItemView的局部刷新。
??假設(shè)下面的一個Demo:

??點擊一下下面灰色的
Button,position為0的ItemView會改變顯示的文字。如果我們不做局部刷新,出現(xiàn)什么問題呢?我們先來試試:
mDataList.get(0).setString("change data");
mAdapter.notifyItemChanged(0);
??正常的實現(xiàn)應(yīng)該就是上面的代碼,非常的簡單,也是我們經(jīng)常書寫的代碼。這樣書寫有什么問題嗎?有很大的問題!就是整個ItemView會閃爍一下,效果如下:

??網(wǎng)上給了一堆的原因分析,我個人覺得,原因非常的簡單,就是第一個ItemView執(zhí)行的change動畫。所以介于這兩個原因,我們可以找到兩種解決方案:
- 設(shè)置
RecyclerView的change動畫時間為0,也就是調(diào)用ItemAnimator的setChangeDuration方法。- 直接將
RecyclerView的ItemAnimator設(shè)置為null。
??對于第二種方案,我不置可否。這樣來想,我們直接將動畫設(shè)置為null,那么RecyclerView就沒有任何動畫,是不是感覺有點得不償失?
??第一種方案比起第二種方案稍微要好一些,我們將change動畫時間設(shè)置為0,只影響了change動畫(相當(dāng)于取消了change動畫),不會影響其他其他操作的動畫。不過,還是感覺美中不足,相當(dāng)于后面所有的change操作都沒有了動畫,如果我想有些change操作有動畫呢?
??此時就需要局部刷新出手了。我們先來看看怎么實現(xiàn)局部刷新:
??首先,調(diào)用帶兩個參數(shù)的notifyItemChanged方法,如下:
mAdapter.notifyItemChanged(0, "");
??第二參數(shù)是一個payload,Object類型,所以我們可以傳遞任意對象,這里就傳遞一個空字符串。
??然后我們得重寫Adapter的onBindViewHolder方法(這里重寫的是帶三個參數(shù)的onBindViewHolder方法,帶兩個參數(shù)的onBindViewHolder該怎么寫就怎么寫)。
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.contains("")) {
holder.mTextView.setText(mDataList.get(position).getString());
} else {
super.onBindViewHolder(holder, position, payloads);
}
}
??這里我們判斷了一下payloads里面是否含有之前我們傳遞的空字符串,如果含有的話,直接更新顯示文字即可,如果不含有則走默認邏輯。現(xiàn)在來我們看看效果:

??是不是感覺perfect?
(2). 局部刷新的實現(xiàn)原理
??局部刷新的使用是非常的簡單的,就是重寫了Adapter帶三個參數(shù)的onBindViewHolder方法,然后調(diào)用的也是帶兩個參數(shù)的notifyItemChanged方法。
??但是,我們不禁好奇,為什么這樣做ItemView就不會閃爍呢?我在這里就可以告訴大家答案,是因為沒有執(zhí)行change動畫。為了保持求知若饑,虛心若愚的良好傳統(tǒng),大家肯定會進一步的問,為什么在這種情況下不會執(zhí)行動畫呢?其實為了回答這個問題,我早就已經(jīng)為大家打好鋪墊,在理解局部刷新的原理之前,大家最好已經(jīng)理解了RecyclerView的動畫機制,有興趣的同學(xué)可以看看我之前的文章:RecyclerView 源碼分析(四) - RecyclerView的動畫機制。
??前面頻繁的源碼追蹤我們這里就不進行了,可以參考我的文章:RecyclerView 源碼分析(四) - RecyclerView的動畫機制,這里直接從根源入手。我們都知道,當(dāng)我們調(diào)用Adapter的notifyItemChanged方法,會執(zhí)行到AdapterHelper$Callback的markViewHoldersUpdated方法。
??而我們這里不看markViewHoldersUpdated方法,而是看哪里調(diào)用了這個方法。
??根據(jù)我們辛苦的追蹤代碼,我們發(fā)現(xiàn)主要是有兩個地方在調(diào)用markViewHoldersUpdated方法:1. postponeAndUpdateViewHolders方法;2. consumeUpdatesInOnePass方法。
??這其中,consumeUpdatesInOnePass方法是我們的老朋友,該方法主要是在dispatchLayoutStep2方法,其作用也是不言而喻,主要是給消費之前添加的operation。而postponeAndUpdateViewHolders方法我們就感覺非常的陌生,這個方法是在哪里被調(diào)用呢?
??根據(jù)我們的追蹤,發(fā)現(xiàn)它的調(diào)用源頭是AdapterHelper的preProcess方法。而preProcess方法又是在哪里被調(diào)用的呢?是在processAdapterUpdatesAndSetAnimationFlags方法:
private void processAdapterUpdatesAndSetAnimationFlags() {
// ······
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
// ······
}
??而processAdapterUpdatesAndSetAnimationFlags方法只在dispatchLayoutStep1方法調(diào)用(這里不考慮非自動測量的情況)。這里,我們就徹底明了。dispatchLayoutStep1方法階段被預(yù)布局階段,也就是說,change操作在預(yù)布局階段就已經(jīng)回調(diào)markViewHoldersUpdated方法。
??而markViewHoldersUpdated方法的作用是啥呢?其實在RecyclerView 源碼分析(四) - RecyclerView的動畫機制,我就已經(jīng)解釋過了,主要作用有兩個:
- 給每個
ViewHolder打了對應(yīng)的flag- 更新每個
ViewHolder的position。
??關(guān)于這兩個作用的分析,flag我們可以直接跳過,position在后面我會詳細的分析。
??從而,我們知道,在預(yù)布局階段,每個ViewHolder的position和flag就已經(jīng)確定了,這個有什么作用呢?還記得我們之前分析RecyclerView的動畫機制說過,在預(yù)布局階段如果條件允許的話,會進行一次布局,也就是會調(diào)用LayoutManager的onLayouyChildren方法。
??而onLayouyChildren方法會做啥呢?我主要介紹兩點(這里以LinearLayoutManager為例):
1.調(diào)用
LayoutManager的detachAndScrapAttachedViews方法,回收所有的ViewHolder,將他們放入四級緩存中。
- 調(diào)用fill方法進行布局。在fill方法調(diào)用流程會調(diào)用
RecyclerView的tryGetViewHolderForPositionByDeadline方法從緩存中獲取ViewHolder。
??這里我們先來看回收部分,我們知道detachAndScrapAttachedViews方法最終會調(diào)用到Recycler的scrapView方法里面去。我們來看看scrapView方法(請大家睜大眼睛,這是尋找答案的第一條線索):
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !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." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
??從這里我們知道,scrapView方法的作用就是ViewHolder分別放到mAttachedScrap和mChanedScrap數(shù)組。這里我們重點關(guān)注canReuseUpdatedViewHolder(holder)這個判斷條件,我們來追蹤這個方案的代碼,最終我們找到了DefaultItemAnimator的canReuseUpdatedViewHolder方法:
@Override
public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
@NonNull List<Object> payloads) {
return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
}
??看到?jīng)]?這里判斷了一下payloads是否為空。這個有什么作用呢?我們回到scrapView方法來,如果payloads不為空的話,當(dāng)前的ViewHolder會被回收到mAttachedScrap。這里,我們一定要記得,當(dāng)ViewHolder的payloads不為空,那么在回收時,ViewHolder會被回收到mAttachedScrap。這個有什么作用呢?這就需要我們?nèi)ふ业诙l線索。
??第二條線索就藏在tryGetViewHolderForPositionByDeadline方法里面。我們來瞅瞅:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// ······
}
??結(jié)合上面的分析,當(dāng)在預(yù)布局階段,也就是dispatchLayoutStep1階段進行布局,通過帶兩個參數(shù)的notifyItemChanged方法進行通知,肯定會在上面的代碼返回一個ViewHolder。也就是說,在這種情況下,變化前后該ItemView的ViewHolder肯定是同一個ViewHolder。
??如上就是第二條線索,那第二條線索有什么作用呢?就得看第三條線索了。那第三條線索在哪里呢?就在dispatchLayoutStep3方法里面。
??我們都知道,dispatchLayoutStep3階段被稱為后布局,主要進行動畫的執(zhí)行,我們來看看我們的change操作會執(zhí)行哪些代碼:
private void dispatchLayoutStep3() {
// ······
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
// ······
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
// ······
}
// ······
}
??change操作肯定會執(zhí)行到如上的代碼,我們在分析動畫機制時就已經(jīng)分析過了。我們來看看animateChange方法:
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
boolean oldHolderDisappearing, boolean newHolderDisappearing) {
// ······
if (oldHolder != newHolder) {
// ······
addAnimatingView(oldHolder);
// ······
}
// ······
}
??看到?jīng)],這就是最終的答案,只有兩個ViewHolder不是同一個對象才會添加一個AnimatingView。
??由于局部刷新的前后,ItemView的是同一個ViewHolder對象,才會導(dǎo)致局部刷新不會執(zhí)行change動畫,才會解決ItemView的閃爍。
??有可能有人又有疑問了,為什么會全局刷新不是同一個ViewHolder呢?我們通過scrapView方法可以知道,如果全局刷新,那么change的ViewHolder會被回收到mChangedScrap數(shù)組里面去,而在tryGetViewHolderForPositionByDeadline方法里面,我們可以知道,只有預(yù)布局階段才會從mChangedScrap數(shù)組里面獲取ViewHolder對象:
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
??所以預(yù)布局階段和正式布局階段同一個ItemView肯定是不同的ViewHolder,從而會執(zhí)行change動畫。
??由于這個問題的答案尋找起來比較麻煩,這里我就針對這個問題做一個簡單的總結(jié):
布局刷新之所以能解決
ItemView的閃爍問題,是因為在局部刷新的情況下,不會執(zhí)行change動畫。而不執(zhí)行的chang動畫的原因是因為在刷新前后都是同一個ViewHolder,并且都是從mAttachedScrap數(shù)組里面獲得,所以在動畫執(zhí)行階段,不會執(zhí)行局部刷新導(dǎo)致的change動畫,進而解決閃爍問題;而全局刷新由于刷新前后不是同一個ViewHolder,所以會執(zhí)行change動畫。
4. ViewHolder的position
??在ViewHolder的內(nèi)部有幾個讓人難以理解的問題,一個是flag,眾多的flag讓人非常的懵逼,這個我在緩存機制那一篇文章,我已經(jīng)做了詳細的總結(jié),有興趣的同學(xué)可以看看我的文章:RecyclerView 源碼分析(三) - RecyclerView的緩存機制;另一個是position,本文來重點分析一下。
??這里主要分析兩個方法,分別是getAdapterPosition和getLayoutPosition,對應(yīng)著ViewHolder內(nèi)部兩個成員變量mPosition和mPreLayoutPosition兩個屬性。
??大家在使用這兩個方法時,應(yīng)該都對這兩個方法有一定的疑問,這里我簡單的解釋一下這兩個方法的區(qū)別(其實我們從這兩個方法的注釋就能看出區(qū)別)。
??我們先來看看這兩個方法的代碼,首先來看一下getAdapterPosition方法:
public final int getAdapterPosition() {
if (mOwnerRecyclerView == null) {
return NO_POSITION;
}
return mOwnerRecyclerView.getAdapterPositionFor(this);
}
??別看getAdapterPosition方法比較麻煩,還調(diào)用了RecyclerView的getAdapterPositionFor方法進行位置的計算。但是它表達的意思是非常簡單的,就是獲取當(dāng)前ViewHolder所綁定ItemView的真實位置。這里的真實位置說的比較籠統(tǒng),這樣來解釋吧,當(dāng)我們remove掉為position為0的item,正常來說,后面ViewHolder的position應(yīng)該都減1。但是RecyclerView處理Adapter的更新采用的延遲處理策略,所以在正式處理之前獲取ViewHolder的位置可能會出現(xiàn)誤差,介于這個原因,getAdapterPosition方法就出現(xiàn)了。
??getAdapterPosition方法是怎樣保證每次計算都是正確的呢?包括在正式處理之前呢?我們知道,在RecyclerView中,延遲處理的實現(xiàn)是在notify階段往一個叫mPendingUpdates數(shù)組里面添加Operation,分別在dispatchLayoutStep1階段或者dispatchLayoutStep2階段進行處理。通過追蹤getAdapterPositionFor方法,我們知道getAdapterPosition方法在計算位置時,考慮到mPendingUpdates數(shù)組的存在,所以在notify階段和dispatchLayoutStep1階段之間(這里假設(shè)dispatchLayoutStep1就會處理),getAdapterPosition方法返回正確的位置。
??而getLayoutPosition方法呢?getLayoutPosition方法就不能保證在notify階段和dispatchLayoutStep1階段之間獲取的位置是正確的。為什么這么說呢?我們來看看getLayoutPosition方法的代碼:
public final int getLayoutPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
??getLayoutPosition方法返回的是mPosition或者mPreLayoutPosition,但是在dispatchLayoutStep1階段之前,還未更新每個ViewHolder的position,所以獲得不一定的是正確(只有在處理mPendingUpdates的操作時,position才會被更新,對應(yīng)著的代碼就是執(zhí)行AdapterHelper$Callback接口的方法)。
??但是getLayoutPosition方法為什么還有存在的必要呢?我們發(fā)現(xiàn)getLayoutPosition方法不會每次都計算,也就是說,getLayoutPosition方法的效率比getAdapterPosition方法高。當(dāng)我們在Adapter這種調(diào)用方法來獲取ViewHolder的位置時,可以優(yōu)先考慮getLayoutPosition方法,因為Adapter的方法回調(diào)階段不在mPendingUpdates處理之前,所以此時getLayoutPosition方法跟getAdapterPosition方法沒有任何區(qū)別了。
??但是需要注意,如果我們在其他地方獲取ViewHolder的position,要特別注意這種情況,因為其他地方不能保證與RecyclerView狀態(tài)同步,這種情況為了保證結(jié)果的正確性,我們應(yīng)該優(yōu)先考慮getAdapterPosition方法。
5. 總結(jié)
??本文到這里差不多就結(jié)束了,在這里我們做一個簡單的總結(jié)
- 之所以局部刷新能解決
ItemView閃爍的問題,是因為局部刷新進行change操作時沒有執(zhí)行change動畫。而沒有執(zhí)行change動畫的原因是因為在預(yù)布局階段和后布局階段,ItemView的ViewHolder是同一個對象。getAdapterPosition方法在任何時候獲取的都是ViewHolder真實的位置,而getLayoutPosition方法只在mPendingUpdates數(shù)組處理之后才能獲取真實的位置。這是兩個方法區(qū)別。
??下一篇文章是分析DiffUtil的實現(xiàn),來看看DiffUtil來怎么實現(xiàn)差量計算的。