1. 程式人生 > >RecyclerView原始碼解析

RecyclerView原始碼解析

一.基本介紹

在平時的開發中我們或多或少地接觸過ListView,所以我們對ListView並不陌生。不過ListView存在某些缺點,所以谷歌又在ListView的基礎上推出了RecyclerView。記得RecyclerView剛推出時,網上許多文章都介紹了它的優點,甚至有人說RecyclerView可以完全取代ListView,我覺得這個說法太絕對了,雖然RecyclerView在ListView的基礎上優化了,但是實際應用中要使用哪個控制元件,還是要根據實際情況來看的。接下來我們從原始碼的角度瞭解RecyclerView,看看其與ListView的而區別。

二.原始碼解析

在看RecyclerView原始碼中的方法之前,我們有必要先看看RecyclerView原始碼中的內部類,這將更好地幫助我們看懂原始碼。所以我們先來看看其內部類。

·LayoutManager

LayoutManager 負責測量和擺放 item view,可以幫助我們實現普通的豎直,水平,網格等列表,在ListView中不存在這個內部類,所以使用ListView時,我們一般只能實現豎直方向列表。
在這個內部類中的大部分方法是實現測量child的大小,我們這裡不展開說。我們只說其中的一個方法,之所以說這個方法是因為我第一次看的時候,理解錯了,之後再看的時候雖然知道了實際的用途,但是卻存在疑問,到現在我也不太理解為什麼要這麼設定。先來看看這個方法吧。

 public void setAutoMeasureEnabled(boolean
enabled) { mAutoMeasure = enabled; }

這個方法很簡單,只是設定mAutoMeasure的值,而mAutoMeasure只是用來標記是否自動測量(將AutoMeasure直接翻譯為自動測量了)。如果是自動測量,那麼就將所有的測量工作都交給LayoutManager來做,這好像就是本來設定LayoutManager的目的。而如果mAutoMeasure值為false,也就是不是自動測量了,根據原始碼裡面的說明,此時就應該由RecyclerView自己對child進行測量了。但是仔細閱讀原始碼和理解之後,發現無論是不是自動測量,最終測量都是由LayoutManager來做。
上面所說的問題也是我存在疑惑的地方,看起來mAutoMeasure這個變數並不是必需的,因為本來layoutManager的職責就包括測量item view。
所以請有理解這個設計意圖的讀者能幫我解惑,或者我上面的理解有錯的地方,懇請指正。

·Adapter

說起Adapter,我們都不陌生,因為在ListView裡面我們就有使用到Adapter,不過,RecyclerView對Adapter進行了改進。
在ListView中,我們可能會在Adapter寫一個內部類ViewHolder,減少不必要的findViewById操作。當然我們能自己選擇要不要做這一步優化。但是在RecyclerView中的Adapter原始碼中存在下面的方法。

 public final VH createViewHolder(ViewGroup parent, int viewType) {
            TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
            final VH holder = onCreateViewHolder(parent, viewType);
            holder.mItemViewType = viewType;
            TraceCompat.endSection();
            return holder;
        }

從這裡看出RecyclerView強制我們必須使用ViewHolder,這樣做有什麼好處呢?ViewHolder快取了相關的View,當我們設定資料時,可以重複利用這些已經快取的子View去顯示不同item的資料,避免不必要的findViewById操作。

·Recycler

Recycler主要管理廢棄或分離的item的複用。這個類主要和LayoutManager配合使用。
接下來介紹這個類中幾個關鍵的變數和方法。

        //與RecyclerView繫結,但是已經標記了要移除或複用的ViewHolder列表
        ArrayList<ViewHolder> mAttachedScrap;

        //與RecyclerView分離的ViewHolder列表
        ArrayList<ViewHolder> mChangedScrap;

        //ViewHolder快取列表    
        ArrayList<ViewHolder> mCachedViews;

        //提供複用ViewHolder池
        RecycledViewPool mRecyclerPool;

        //開發者控制的ViewHolder快取
        ViewCacheExtension mViewCacheExtension;

        public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }   

getViewForPosition(int):獲取某個位置需要展示的View,先檢查是否有可複用的View,如果沒有就建立新的View並返回。
上面的具體過程如下:
a.檢查mChangedScrap,若匹配到則返回相應的ViewHolder。
b.檢查mAttachedScrap,操作同上。
c.檢查mViewCacheExtension,操作同上。
d.檢查mRecycledPool,操作同上。
e.如果上述操作都沒有成功,則執行Adapter.createViewHolder(),新建ViewHolder例項。
f.返回holder.itemView。

為什麼會按照上面次序檢查呢?這涉及到RecyclerView中的快取。一般系統預先提供了兩級快取,如果有特殊需求,可實現三級快取。這三級快取如下:
·第一級快取:這級快取主要用到的儲存容器有mCacheViews,mAttachedScrap和mChangedScrap。
·第二級快取:這級快取不是系統預先提供的,而是利用ViewCacheExtension充當附加的快取池,當RecyclerView從第一級快取找不到需要的View時,將會從ViewCacheExtension中找,這個快取時由開發者維護的,如果開發者不顯示設定它,則預設不會啟用。只有當我們有特殊需求時(例如在呼叫系統的快取池前返回一個特定的檢視),才會使用這級快取。
·第三級快取:這級的快取主要用到最強大的快取器RecycledViewPool。

·RecycledViewPool

上面說到RecycledViewPool是最強大的快取器,之所以這麼說是因為RecycledViewPool使我們能在RecycledView之間共享ViewHolder,所以其一般能繫結多個Adapter。

說完RecycledView中的內部類,接下來說說其內部的方法。RecycledView是一個控制元件,所以我們還是按照View的繪製流程來看RecycledView的原始碼。

·onMeasure

protected void onMeasure(int widthSpec, int heightSpec) {
       //如果LayoutManager為空則先初始化
        if (mLayout == null) {
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.mAutoMeasure) {
            ...
                //如果RecycledView是第一次進行測量則呼叫 dispatchLayoutStep1()
            if (mState.mLayoutStep == State.STEP_START) {
                dispatchLayoutStep1();
            }
            ...
            dispatchLayoutStep2();

            //只要RecycledView中有一個child無法得到確切的寬高,就要重新測量
            if (mLayout.shouldMeasureTwice()) {
                  ...
                dispatchLayoutStep2();

            }
        } else {
            if (mHasFixedSize) {
                mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                return;
            }
           ...
        }
    }

上面只貼出了onMeasure的部分程式碼,從上面我們可以看出在測量過程中會呼叫dispatchLayoutStep1()和dispatchLayoutStep2()方法,所以接下來我們分別看看這兩個方法。

     /**
      根據原始碼的說明,我們可以知道這個方法會找出那些不需要移除的child,然後預先設定它們的位置
     */
     private void dispatchLayoutStep1() {
         ...
        if (mState.mRunSimpleAnimations) {      
            int count = mChildHelper.getChildCount();
            //遍歷找出不需要移除的child
            for (int i = 0; i < count; ++i) {
                 ....
                }
            }
        //預先設定不需要移除的child的位置
        if (mState.mRunPredictiveAnimations) {
              ...
            mLayout.onLayoutChildren(mRecycler, mState);
              ...
            for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
                final View child = mChildHelper.getChildAt(i);
                final ViewHolder viewHolder = getChildViewHolderInt(child);
                if (viewHolder.shouldIgnore()) {
                    continue;
                }
                if (!mViewInfoStore.isInPreLayout(viewHolder)) {
                    int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
                    boolean wasHidden = viewHolder
                            .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (!wasHidden) {
                        flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                    }
                    final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                            mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
                    if (wasHidden) {
                        recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
                    } else {
                        mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
                    }
                }
            }
            ...
        } else {
            clearOldPositions();
        }
       ...
    }
 /**
    在這個方法裡面才真正根據View測得的狀態去設定其位置,這個方法可能會被多次呼叫(比如當Adapter中的資料發生改變),不過經過這個方法
    之後,View的寬高完全確定下來了。
*/
 private void dispatchLayoutStep2() {
        eatRequestLayout();
        onEnterLayoutOrScroll();
        mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
        mAdapterHelper.consumeUpdatesInOnePass();
        mState.mItemCount = mAdapter.getItemCount();
        mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

        // Step 2: Run layout
        mState.mInPreLayout = false;
        mLayout.onLayoutChildren(mRecycler, mState);

        mState.mStructureChanged = false;
        mPendingSavedState = null;

        // onLayoutChildren may have caused client code to disable item animations; re-check
        mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
        mState.mLayoutStep = State.STEP_ANIMATIONS;
        onExitLayoutOrScroll();
        resumeRequestLayout(false);
    }

·onLayout():在onLayout中會呼叫dispatchLayout()方法, dispatchLayout()實際就像layoutChildren()的封裝,在這個方法完成對各個child位置的設定。具體實現還是呼叫了上面所說的dispatchLayoutStep1()和dispatchLayoutStep2()。在確保了這兩個方法已經呼叫之後,還會呼叫dispatchLayoutStep3()。
dispatchLayoutStep3()主要是儲存所有View相關的動畫資訊,然後觸發動畫和做一些必要的清理。

·onDraw():只是遍歷了所有的child並呼叫他們的onDraw()方法。

關於RecycledView的部分原始碼介紹完了,通過學習原始碼和日常開發的使用,我們都能發現RecycledView和ListView的區別,雖然RecycledView比ListView效能好了一些,在佈局方面也靈活許多,但個人覺得RecycledView在使用上比ListView麻煩了一些。所以實際開發中選擇哪一個並不是絕對的,根據實際選擇合適的就行了,也不用一昧的擡高RecycledView的地位。

相關推薦

【進階】RecyclerView原始碼解析(二)——快取機制

引言 接著上一篇部落格分析完RecyclerView的繪製流程,其實對RecyclerView已經有了一個大體的瞭解,尤其是RecyclerView和LayoutManager和ItemDecoration的關係。 本篇文章將對RecyclerVie

【進階】RecyclerView原始碼解析(三)——深度解析快取機制

上一篇部落格從原始碼角度分析了RecyclerView讀取快取的步驟,讓我們對於RecyclerView的快取有了一個初步的理解,但對於RecyclerView的快取的原理還是不能理解。本篇部落格將從實際專案角度來理解RecyclerView的快取原理。

最全面的RecyclerView原始碼解析

相信很多人用RecyclerView已經很久了,但還是不得不感嘆 RecyclerView的強大,效能、擴充套件性等方面都很強大。網上看了很多原始碼方面對RecyclerView,覺得還不夠全面,而且自己不走一遍原始碼總感覺會很容易忘記。 開啟Recycler

RecyclerView原始碼解析

一.基本介紹 在平時的開發中我們或多或少地接觸過ListView,所以我們對ListView並不陌生。不過ListView存在某些缺點,所以谷歌又在ListView的基礎上推出了RecyclerView。記得RecyclerView剛推出時,網上許多文章都介紹

【進階】RecyclerView原始碼解析(一)——繪製流程

引言 自從Google出了RecyclerView後,基本上列表的場景已經完全替代了原來的ListView和GridView,現在不僅僅是列表,多樣式(俗稱蓋樓),複雜頁面等,只要我們願意,RecyclerView幾乎可以代替實現80%的佈局,Git

RecyclerView 的常用方法;工作原理與ListView比較;原始碼解析

寫在前面 本文原創,轉載請以連結形式註明地址:http://kymjs.com/code/2016/07/10/01起深入淺出這名字的時候我是慎重又慎重的,生怕被人罵標題黨,寫的什麼破玩意還敢說深入淺出。所以還是請大家不要抱著太高的期望,因為沒有期望就沒有失望,就像陳潤說的

RecyclerView 原始碼分析(一) —— 繪製流程解析

概述 對於 RecyclerView 是那麼熟悉又那麼陌生。熟悉是因為作為一名 Android 開發者,RecyclerView 是經常會在專案裡面用到的,陌生是因為只是知道怎麼用,但是卻不知道 RecyclerView 的內部實現機制。 但凡是一位有所追求的開發者,都不會只讓自己停留在只會使用上,

Netty進階:Futrue&Promise原始碼解析

文章目錄 1. Future&Promise 2. AbstractFuture 3.Completefuture 4.Channelfuture&Completechannel

大資料基礎(1)zookeeper原始碼解析

五 原始碼解析   public enum ServerState { LOOKING, FOLLOWING, LEADING, OBSERVING;}zookeeper伺服器狀態:剛啟動LOOKING,follower是FOLLOWING,leader是LEADING,observer是

Android框架原始碼解析之(四)Picasso

這次要分析的原始碼是 Picasso 2.5.2 ,四年前的版本,用eclipse寫的,但不影響這次我們對其原始碼的分析 地址:https://github.com/square/picasso/tree/picasso-parent-2.5.2 Picasso的簡單使用

Android框架原始碼解析之(三)ButterKnife

注:所有分析基於butterknife:8.4.0 原始碼目錄:https://github.com/JakeWharton/butterknife 其中最主要的3個模組是: Butterknife註解處理器https://github.com/JakeWharton/

Android框架原始碼解析之(二)OKhttp

原始碼在:https://github.com/square/okhttp 包實在是太多了,OKhttp核心在這塊https://github.com/square/okhttp/tree/master/okhttp 直接匯入Android Studio中即可。 基本使用:

Android框架原始碼解析之(一)Volley

前幾天面試CVTE,HR面掛了。讓內部一個學長幫我查看了一下面試官評價,發現二面面試官的評價如下: 廣度OK,但缺乏深究能力,深度與實踐不足 原始碼:只能說流程,細節程式碼不清楚,retrofit和volley都是。 感覺自己一方面:自己面試技巧有待提高吧(框

HashMap原始碼解析(JDK8)

前言 這段時間有空,專門填補了下基礎,把常用的ArrayList、LinkedList、HashMap、LinkedHashMap、LruCache原始碼看了一遍,List相對比較簡單就不單獨介紹了,Map準備用兩篇的篇幅,分別介紹HashMap和(LruCache+LinkedHa

原始碼解析--Long、long型別的比較遇到的問題

Long、long型別的比較遇到的問題: 1、long 是基本型別 Long是物件型別。 public static void main(String[] args) { Long A = 127l; Long B = 127l; long C = 127; l

CopyOnWriteArrayList實現原理以及原始碼解析

CopyOnWriteArrayList實現原理以及原始碼解析 1、CopyOnWrite容器(併發容器) Copy-On-Write簡稱COW,是一種用於程式設計中的優化策略。 其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,才

LinkedList實現原理以及原始碼解析(1.7)

LinkedList實現原理以及原始碼解析(1.7) 在1.7之後,oracle將LinkedList做了一些優化, 將1.6中的環形結構優化為了直線型了連結串列結構。 1、LinkedList定義: public class LinkedList<E>

ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8)

ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8) ArrayList的基本知識在上一節已經討論過,這節主要看ArrayList在JDK1.6到1.8的一些實現變化。 JDK版本不一樣,ArrayList類的原始碼也不一樣。 1、ArrayList類結構:

ArrayList實現原理以及原始碼解析(JDK1.6)

ArrayList實現原理以及原始碼解析(JDK1.6) 1、ArrayList ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只能用在單執行緒環境下。

ConcurrentHashMap實現原理以及原始碼解析

ConcurrentHashMap實現原理以及原始碼解析 ConcurrentHashMap是Java1.5中引用的一個執行緒安全的支援高併發的HashMap集合類。 1、執行緒不安全的HashMap 因為多執行緒環境下,使用Hashmap進行put操作會引起死迴圈