RecyclerView 源碼分析(五) - Adapter的源碼分析

??熟悉RecyclerView的同學(xué)應(yīng)該都知道,Adapter作為RecyclerView四大組成部分(Adapter,LayoutManagerItemAnimatorItemDecoration)之一,其重要性自然是不言而喻。今天,我們來分析一下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)前RecyclerViewItemView是否擁有固定id,跟getItemId方法一起使用。如果設(shè)置為true,會提高RecyclerView的緩存效率。

??上表中所列的方法應(yīng)該就是我們使用Adapter經(jīng)常使用的方法,接下來,我將正式分析Adapter的相關(guān)代碼。我打算從如下角度來分析:

  1. 重新從RecyclerView緩存角度來分析onCreateViewHolderonBindViewHolder。
  2. onBindViewHolder的一個重載方法--主要是用于局部刷新。
  3. 結(jié)合Adapter,分析ViewHolder的position。

1. onCreateViewHolder和onBindViewHolder

??onCreateViewHolder方法和onBindViewHolder方法算是我們使用次數(shù)最多的方法,很多自定義Adapter的框架也都是從這兩個方法入手的。我們來看看這兩個方法到底有什么作用。

(1).onCreateViewHolder

??首先,我們來看一下onCreateViewHolder方法,從它的調(diào)用時機入手。
??在本文之前,我分析過RecyclerView的緩存機制,當(dāng)時我將RecyclerView的緩存分為4級緩存,其中分別是:

  1. 一級緩存:scrap數(shù)組
  2. 二級緩存:CachedView
  3. 三級緩存:ViewCacheExtension
  4. 四級緩存:RecyclerViewPool

??LayoutManager會獲取ViewHolder時,如果4級緩存都沒有命中,就會調(diào)用AdapteronCreateViewHolder方法來創(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");
                    }
                }

??上面的代碼是RecyclerViewtryGetViewHolderForPositionByDeadline方法代碼片段。之前,我們在分析緩存機制時,就已經(jīng)仔細分析這個方法,這里我就不再贅述,有興趣的同學(xué)可以我之前的文章:RecyclerView 源碼分析(三) - RecyclerView的緩存機制。
??我們回到上面的代碼片段中來,從上面的代碼上,我們看到這里是調(diào)用的是AdaptercreateViewHolder方法來創(chuàng)建ViewHolder。我們來看看AdaptercreateViewHolder方法:

        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)代理模式中的繼承代理。然后,我們來看看AdapterbindViewHolder方法:

        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方法之前,更新了ViewHoldermPosition屬性和給ViewHolder設(shè)置了一些flag;在執(zhí)行onBindViewHolder方法之后,清理了ViewHolderpayload,并且還是給ItemViewLayoutParamsmInsetsDirty屬性設(shè)置為true。
??這里額外的提出兩個點:

  1. payload主要是用于局部刷新的,待會我會詳細解釋怎么進行局部刷新。
  2. 關(guān)于LayoutParamsmInsetsDirty屬性,這個屬性尤為重要的,主要用于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動畫。所以介于這兩個原因,我們可以找到兩種解決方案:

  1. 設(shè)置RecyclerView的change動畫時間為0,也就是調(diào)用ItemAnimatorsetChangeDuration方法。
  2. 直接將RecyclerViewItemAnimator設(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類型,所以我們可以傳遞任意對象,這里就傳遞一個空字符串。
??然后我們得重寫AdapteronBindViewHolder方法(這里重寫的是帶三個參數(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)用AdapternotifyItemChanged方法,會執(zhí)行到AdapterHelper$CallbackmarkViewHoldersUpdated方法。
??而我們這里不看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)用源頭是AdapterHelperpreProcess方法。而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)解釋過了,主要作用有兩個:

  1. 給每個ViewHolder打了對應(yīng)的flag
  2. 更新每個ViewHolder的position。

??關(guān)于這兩個作用的分析,flag我們可以直接跳過,position在后面我會詳細的分析。
??從而,我們知道,在預(yù)布局階段,每個ViewHolder的position和flag就已經(jīng)確定了,這個有什么作用呢?還記得我們之前分析RecyclerView的動畫機制說過,在預(yù)布局階段如果條件允許的話,會進行一次布局,也就是會調(diào)用LayoutManageronLayouyChildren方法。
??而onLayouyChildren方法會做啥呢?我主要介紹兩點(這里以LinearLayoutManager為例):

1.調(diào)用LayoutManagerdetachAndScrapAttachedViews方法,回收所有的ViewHolder,將他們放入四級緩存中。

  1. 調(diào)用fill方法進行布局。在fill方法調(diào)用流程會調(diào)用RecyclerViewtryGetViewHolderForPositionByDeadline方法從緩存中獲取ViewHolder。

??這里我們先來看回收部分,我們知道detachAndScrapAttachedViews方法最終會調(diào)用到RecyclerscrapView方法里面去。我們來看看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分別放到mAttachedScrapmChanedScrap數(shù)組。這里我們重點關(guān)注canReuseUpdatedViewHolder(holder)這個判斷條件,我們來追蹤這個方案的代碼,最終我們找到了DefaultItemAnimatorcanReuseUpdatedViewHolder方法:

    @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)ViewHolderpayloads不為空,那么在回收時,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。也就是說,在這種情況下,變化前后該ItemViewViewHolder肯定是同一個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,本文來重點分析一下。
??這里主要分析兩個方法,分別是getAdapterPositiongetLayoutPosition,對應(yīng)著ViewHolder內(nèi)部兩個成員變量mPositionmPreLayoutPosition兩個屬性。
??大家在使用這兩個方法時,應(yīng)該都對這兩個方法有一定的疑問,這里我簡單的解釋一下這兩個方法的區(qū)別(其實我們從這兩個方法的注釋就能看出區(qū)別)。
??我們先來看看這兩個方法的代碼,首先來看一下getAdapterPosition方法:

        public final int getAdapterPosition() {
            if (mOwnerRecyclerView == null) {
                return NO_POSITION;
            }
            return mOwnerRecyclerView.getAdapterPositionFor(this);
        }

??別看getAdapterPosition方法比較麻煩,還調(diào)用了RecyclerViewgetAdapterPositionFor方法進行位置的計算。但是它表達的意思是非常簡單的,就是獲取當(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ū)別了。
??但是需要注意,如果我們在其他地方獲取ViewHolderposition,要特別注意這種情況,因為其他地方不能保證與RecyclerView狀態(tài)同步,這種情況為了保證結(jié)果的正確性,我們應(yīng)該優(yōu)先考慮getAdapterPosition方法。

5. 總結(jié)

??本文到這里差不多就結(jié)束了,在這里我們做一個簡單的總結(jié)

  1. 之所以局部刷新能解決ItemView閃爍的問題,是因為局部刷新進行change操作時沒有執(zhí)行change動畫。而沒有執(zhí)行change動畫的原因是因為在預(yù)布局階段和后布局階段,ItemViewViewHolder是同一個對象。
  2. getAdapterPosition方法在任何時候獲取的都是ViewHolder真實的位置,而getLayoutPosition方法只在mPendingUpdates數(shù)組處理之后才能獲取真實的位置。這是兩個方法區(qū)別。

??下一篇文章是分析DiffUtil的實現(xiàn),來看看DiffUtil來怎么實現(xiàn)差量計算的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容