1. 程式人生 > >RecyclerView原始碼學習筆記(一)建構函式和setLayoutManager方法

RecyclerView原始碼學習筆記(一)建構函式和setLayoutManager方法

前言

RecyclerView已經出來很久,現在幾乎應該都會用RecyclerView代替Listview,雖然我覺得大多數人應該還是不太清楚這兩者之前的區別的,或者說RecyclerView相對於Listview到底好在哪裡。我平時也只是很簡單的使用一下,並沒有對其原理進行深度挖掘,現在剛好公司專案不忙,就花點時間研究一下它的原始碼。

內容

類繼承關係

我覺得研究任何一個類的原始碼首先應該知道其類的繼承關係,這樣我們可以對它有一個整體的認識,比如TextView繼承自View,那它就會有View的一些特性。所以先來看下RecyclerView的繼承關係:
這裡寫圖片描述

可以看到它直接繼承於ViewGroup,所以它是個容器(廢話,哈哈),還有它的子類有BaseGridView,WearableRecyclerView,HorizontalGridView,VerticalGridView,我們暫時這裡不研究子類。

類註釋

除了類繼承關係,類註釋也是很重要的部分,因為它往往介紹了這個類的特性,以及一些關鍵概念,我們這裡就來看下RecyclerView的註釋,原文如下

A flexible view for providing a limited window into a large data set.
Glossary of terms:
Adapter: A subclass of RecyclerView.Adapter responsible for providing views that represent items in a data set.
Position: The position of a data item within an Adapter.
Index: The index of an attached child view as used in a call to getChildAt(int). Contrast with Position.
Binding: The process of preparing a child view to display data corresponding to a position within the adapter.
Recycle (view): A view previously used to display data for a specific adapter position may be placed in a cache for later reuse to display the same type of data again later. This can drastically improve performance by skipping initial layout inflation or construction.
Scrap (view): A child view that has entered into a temporarily detached state during layout. Scrap views may be reused without becoming fully detached from the parent RecyclerView, either unmodified if no rebinding is required or modified by the adapter if the view was considered dirty.
Dirty (view): A child view that must be rebound by the adapter before being displayed.
Positions in RecyclerView:
RecyclerView introduces an additional level of abstraction between the RecyclerView.Adapter and RecyclerView.LayoutManager to be able to detect data set changes in batches during a layout calculation. This saves LayoutManager from tracking adapter changes to calculate animations. It also helps with performance because all view bindings happen at the same time and unnecessary bindings are avoided.

For this reason, there are two types of position related methods in RecyclerView:

layout position: Position of an item in the latest layout calculation. This is the position from the LayoutManager’s perspective.
adapter position: Position of an item in the adapter. This is the position from the Adapter’s perspective.
These two positions are the same except the time between dispatching adapter.notify* events and calculating the updated layout.

Methods that return or receive LayoutPosition use position as of the latest layout calculation (e.g. getLayoutPosition(), findViewHolderForLayoutPosition(int)). These positions include all changes until the last layout calculation. You can rely on these positions to be consistent with what user is currently seeing on the screen. For example, if you have a list of items on the screen and user asks for the 5th element, you should use these methods as they’ll match what user is seeing.

The other set of position related methods are in the form of AdapterPosition. (e.g. getAdapterPosition(), findViewHolderForAdapterPosition(int)) You should use these methods when you need to work with up-to-date adapter positions even if they may not have been reflected to layout yet. For example, if you want to access the item in the adapter on a ViewHolder click, you should use getAdapterPosition(). Beware that these methods may not be able to calculate adapter positions if notifyDataSetChanged() has been called and new layout has not yet been calculated. For this reasons, you should carefully handle NO_POSITION or null results from these methods.

When writing a RecyclerView.LayoutManager you almost always want to use layout positions whereas when writing an RecyclerView.Adapter, you probably want to use adapter positions.

呵呵,辣麼大一串,就算是中文都懶的看,何況是英文,可四既然是寫部落格,就是要做出貢獻,只能咬緊牙關看下去。經過吭哧吭哧的閱讀翻譯,大概意思如下:

RecyclerView是一個靈活的view,用來在有限的視窗中顯示大量的資料集。呵呵,官方文件這麼寫,我也很絕望。
在真正開始閱讀原始碼前,先介紹幾個關鍵名詞:

Adapter:RecyclerView.Adapter的子類,用來提供顯示資料條目的檢視(從介紹來看和ListView的adapter差不多)

Position:adapter中資料條目的位置

Index:已經新增的子view的索引,也就是item檢視,在getChildAt(int)會被用到。和position要區別開來,position是資料的位置,index是檢視的位置

Binding: 將adapter中的資料顯示到每一個child view中的過程。

Recycle (view):就是一個可以複用的view,這可以大幅提高效能,因為省去了初始化和構造的過程

Scrap (view):什麼是Scrap view呢?就是指的那些還沒有被detached,但是已經被標記為removal或者reuse,Scrap views被複用的時候有兩種情況,一種是完全不改變內容,因為資料沒有發生改變,另外一種是當這個view被認是dirty,的時候,這樣就要重新繫結資料(從後面程式碼來看,其實scrap的view指的是那些已經呼叫了detach方法,但並沒有被remove的view,只是將parent設為null,在檢視中仍然存在)

Dirty (view): dirty view指的是那些在展示之前必須重新繫結資料的view

上面提到的view都是指RecyclerView中的item view

關鍵名詞介紹到這裡,大家應該都看的懂。

接下來重點介紹了RecyclerView中positions概念

大概意思是Recyclerview在RecyclerView.Adapter和RecyclerView.LayoutManager之間採用了一種額外的抽象,使得可以在佈局計算的時候偵測到大批量資料的變化,這可以將LayoutManager從跟蹤adapter的資料變化中解脫出來,而去從事計算動畫的工作。( to calculate animations這段意思不確定,特別是to在這裡的意思,先放著,等看了原始碼再說)。這樣還可以提高效能表現,因為所有的view binding在同一時間完成,避免了不要的binding(一臉悶逼,還是先放著)。

因為這個原因,所以在RecyclerView中有兩類position相關的方法。

layout position: 在最近一次佈局計算後item的位置,這個位置是站在LayoutManager的角度來說
adapter position: item在adpater中的位置,這是站在Adapter的角度來說

個人認為layout position就是指檢視上item的位置,而adapter position就是指在data set中的位置
這兩個position在大多數時候是相等的,只有當adapter.notify*已經被呼叫,而佈局還沒有重新整理之前這段時間是不一樣的。

那些返回或者接受*LayoutPosition*的方法使用的是最近一次佈局計算的位置,比如getLayoutPosition()findViewHolderForLayoutPosition(int)。這些position就是使用者在螢幕上看到的樣子。舉個例子,如果你有一個list在螢幕上展示,然後使用者想要第五個條目,你就應該使用這些方法。

另外一系列position相關的方法格式是這樣的*AdapterPosition*,如getAdapterPosition(), findViewHolderForAdapterPosition(int)。如果你需要和最新的adapter position打交道,不管它是否已經反映到佈局中,那你就應該使用這系列方法。比如,如果你希望在ViewHolder click中訪問adapter中的item,你就應該使用getAdapterPosition(),有一點要注意,就是當notifyDataSetChanged已經被呼叫,而佈局還沒有計算完成,這時候就不能使用這些方法去計算adapter的position。所以,當你在使用這些方法的時候需要特別注意處理返回值為NO_POSITION 的情況。

總的來說就是當你在寫RecyclerView.LayoutManager的時候,基本上就應該使用layout positions,而在寫Adapter的時候就應該使用adapter positions

你看,仔細看一下注釋還是很有用的吧。

建構函式

好了接下來就是真正開始啃原始碼的時候了。首先當然從建構函式開始,既然是繼承自View,那肯定也就是少不了View類似的那些建構函式。
這裡寫圖片描述

先從RecyclerView(Context context)看起。

 public RecyclerView(Context context) {
        this(context, null);
    }

直接呼叫RecyclerView(Context context, AttributeSet attrs),

public RecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

所以最後就看public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle)
原始碼不是很多,就全部貼上來了

 public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
            mClipToPadding = a.getBoolean(0, true);
            a.recycle();
        } else {
            mClipToPadding = true;
        }
        setScrollContainer(true);
        setFocusableInTouchMode(true);

        final ViewConfiguration vc = ViewConfiguration.get(context);
        mTouchSlop = vc.getScaledTouchSlop();
        mScaledHorizontalScrollFactor =
                ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);
        mScaledVerticalScrollFactor =
                ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);
        mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
        setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);

        mItemAnimator.setListener(mItemAnimatorListener);
        initAdapterManager();
        initChildrenHelper();
        // If not explicitly specified this view is important for accessibility.
        if (ViewCompat.getImportantForAccessibility(this)
                == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            ViewCompat.setImportantForAccessibility(this,
                    ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
        }
        mAccessibilityManager = (AccessibilityManager) getContext()
                .getSystemService(Context.ACCESSIBILITY_SERVICE);
        setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
        // Create the layoutManager if specified.

        boolean nestedScrollingEnabled = true;

        if (attrs != null) {
            int defStyleRes = 0;
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
                    defStyle, defStyleRes);
            String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
            int descendantFocusability = a.getInt(
                    R.styleable.RecyclerView_android_descendantFocusability, -1);
            if (descendantFocusability == -1) {
                setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            }
            mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false);
            if (mEnableFastScroller) {
                StateListDrawable verticalThumbDrawable = (StateListDrawable) a
                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable);
                Drawable verticalTrackDrawable = a
                        .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable);
                StateListDrawable horizontalThumbDrawable = (StateListDrawable) a
                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable);
                Drawable horizontalTrackDrawable = a
                        .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable);
                initFastScroller(verticalThumbDrawable, verticalTrackDrawable,
                        horizontalThumbDrawable, horizontalTrackDrawable);
            }
            a.recycle();
            createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);

            if (Build.VERSION.SDK_INT >= 21) {
                a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
                        defStyle, defStyleRes);
                nestedScrollingEnabled = a.getBoolean(0, true);
                a.recycle();
            }
        } else {
            setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        }

        // Re-set whether nested scrolling is enabled so that it is set on all API levels
        setNestedScrollingEnabled(nestedScrollingEnabled);
    }

首先是呼叫父類構造方法,不鳥他,繼續往下,

 if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
            mClipToPadding = a.getBoolean(0, true);
            a.recycle();
        } else {
            mClipToPadding = true;
        }

就是初始化了mClipToPadding變數。

   setScrollContainer(true);

這是為scroll容器,設定為true後如果開啟軟體盤,view會被壓縮。

   setFocusableInTouchMode(true);

   final ViewConfiguration vc = ViewConfiguration.get(context);
   mTouchSlop = vc.getScaledTouchSlop();
   mScaledHorizontalScrollFactor =
           ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context);
   mScaledVerticalScrollFactor =
           ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context);
   mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
   mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
   setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);

基礎設定,不管它

mItemAnimator.setListener(mItemAnimatorListener);

設定itemAnimatorListener,當item動畫結束的時候,這個listeneronAnimationFinished方法必須被呼叫

initAdapterManager();

看方法程式碼,就是初始化了一個AdapterHelper例項,這個AdapterHelper類是幹什麼的呢?看類註釋是說可以將adapter的更新動作加入佇列,並進行處理,先不深究,等下回頭再來看。

initChildrenHelper();

看程式碼是初始化了一個ChildHelper例項,這個類操作child view的中間層,具體可以看這篇文章 RecyclerView機制解析: ChildHelper
接下來,我們忽略通用code,只看RecyclerView特有code

String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);

獲取LayoutManagerName,所以LayoutManagerName是可以通過xml指定的。

createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);

上面從xml中獲取到的layoutManagerName就在這裡派上用場了,如果layoutManagerNamenull,就直接return了。如果不為null,則通過類名來例項化一個LayoutManager,並呼叫setLayoutManager方法。

 private void createLayoutManager(Context context, String className, AttributeSet attrs,
            int defStyleAttr, int defStyleRes) {
        if (className != null) {
            className = className.trim();
            if (!className.isEmpty()) {
                className = getFullClassName(context, className);
                try {
                    ClassLoader classLoader;
                    if (isInEditMode()) {
                        // Stupid layoutlib cannot handle simple class loaders.
                        classLoader = this.getClass().getClassLoader();
                    } else {
                        classLoader = context.getClassLoader();
                    }
                    Class<? extends LayoutManager> layoutManagerClass =
                            classLoader.loadClass(className).asSubclass(LayoutManager.class);
                    Constructor<? extends LayoutManager> constructor;
                    Object[] constructorArgs = null;
                    try {
                        constructor = layoutManagerClass
                                .getConstructor(LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE);
                        constructorArgs = new Object[]{context, attrs, defStyleAttr, defStyleRes};
                    } catch (NoSuchMethodException e) {
                        try {
                            constructor = layoutManagerClass.getConstructor();
                        } catch (NoSuchMethodException e1) {
                            e1.initCause(e);
                            throw new IllegalStateException(attrs.getPositionDescription()
                                    + ": Error creating LayoutManager " + className, e1);
                        }
                    }
                    constructor.setAccessible(true);
                    setLayoutManager(constructor.newInstance(constructorArgs));
                } catch (ClassNotFoundException e) {
                    throw new IllegalStateException(attrs.getPositionDescription()
                            + ": Unable to find LayoutManager " + className, e);
                } catch (InvocationTargetException e) {
                    throw new IllegalStateException(attrs.getPositionDescription()
                            + ": Could not instantiate the LayoutManager: " + className, e);
                } catch (InstantiationException e) {
                    throw new IllegalStateException(attrs.getPositionDescription()
                            + ": Could not instantiate the LayoutManager: " + className, e);
                } catch (IllegalAccessException e) {
                    throw new IllegalStateException(attrs.getPositionDescription()
                            + ": Cannot access non-public constructor " + className, e);
                } catch (ClassCastException e) {
                    throw new IllegalStateException(attrs.getPositionDescription()
                            + ": Class is not a LayoutManager " + className, e);
                }
            }
        }
    }

到這裡建構函式就完了。

setLayoutManager方法

按照一般的使用方法,接下來我們需要通過setLayoutManager方法手動指定一個LayoutManager,方法原始碼如下:

    public void setLayoutManager(LayoutManager layout) {
        if (layout == mLayout) {
            return;
        }
        stopScroll();
        // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
        // chance that LayoutManagers will re-use views.
        if (mLayout != null) {
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
            mRecycler.clear();

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();
        }
        // this is just a defensive measure for faulty item animators.
        mChildHelper.removeAllViewsUnfiltered();
        mLayout = layout;
        if (layout != null) {
            if (layout.mRecyclerView != null) {
                throw new IllegalArgumentException("LayoutManager " + layout +
                        " is already attached to a RecyclerView: " + layout.mRecyclerView);
            }
            mLayout.setRecyclerView(this);
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        mRecycler.updateViewCacheSize();
        requestLayout();
    }

首先會判斷新的LayoutManager是不是和舊的一樣,一樣就直接返回,否則就往下走。主要分為以下幾個步驟

  1. 停止當前的滾動
  2. 停止所有item動畫(mItemAnimator.endAnimations()),ItemAnimator的預設是DefaultItemAnimator,這個可以放在以後研究下
  3. 移除所有子view,並回收
  4. 移除和回收所有scrape view
  5. 清除所有放在Recycler中的view
  6. 如果RecyclerView已經attach到window就呼叫dispatchDetachedFromWindow方法
  7. 將舊的Layoutmanager的RecyclerView設定null
  8. 呼叫childhelper的removeAllViewsUnfiltered方法(其實就是從RecyclerView中移除所有child)
  9. 將新的layout賦值給mLayout
  10. 將RecyclerView賦值給新的Layoutmanager並呼叫新的Layoutmanager的dispatchAttachedToWindow方法。
  11. 呼叫mRecycler.updateViewCacheSize()並呼叫requestLayout

    注意,上面1-8步的動作都是在舊的layoutmanager上完成。

我們一個一個方法看下去,誰叫這是真正的原始碼學習呢。。。

    public void stopScroll() {
        setScrollState(SCROLL_STATE_IDLE);
        stopScrollersInternal();
    }
     void setScrollState(int state) {
        if (state == mScrollState) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
                    new Exception());
        }
        mScrollState = state;
        if (state != SCROLL_STATE_SETTLING) {
            stopScrollersInternal();
        }
        dispatchOnScrollStateChanged(state);
    }

先看setScrollState方法,首先會把mScrollState的值設定為SCROLL_STATE_IDLE(指當前view沒有滑動),然後如果看到state不等於SCROLL_STATE_SETTLING就呼叫stopScrollersInternal(),明顯我們這裡就不是SCROLL_STATE_SETTLING,所以呼叫stopScrollersInternal()方法,stopScrollersInternal()方法如下

    private void stopScrollersInternal() {
        mViewFlinger.stop();
        if (mLayout != null) {
            mLayout.stopSmoothScroller();
        }
    }

就是呼叫ViewFlinger的stop方法,並呼叫LayoutManager的stopSmoothScroller()方法,ViewFlinger的方法如下

    public void stop() {
        removeCallbacks(this);
        mScroller.abortAnimation();
    }

首先要知道ViewFlinger是一個Runnable,主要處理RecyclerView的快速滑動,removeCallbacks(this)就是將此runnable從UI執行緒的訊息佇列中移除,而mScroller是一個OverScroll,abortAnimation方法就是直接將狀態設定到動畫的結束值。這個還是比較簡單。我們來看下LayoutManager的stopSmoothScroller方法,其實裡面呼叫了mSmoothScroller.stop(),而這個SmoothScroller.stop()方法內部如下

  protected final void stop() {
            if (!mRunning) {
                return;
            }
            onStop();
            mRecyclerView.mState.mTargetPosition = RecyclerView.NO_POSITION;
            mTargetView = null;
            mTargetPosition = RecyclerView.NO_POSITION;
            mPendingInitialRun = false;
            mRunning = false;
            // trigger a cleanup
            mLayoutManager.onSmoothScrollerStopped(this);
            // clear references to avoid any potential leak by a custom smooth scroller
            mLayoutManager = null;
            mRecyclerView = null;
        }

大致意思是將資源都清空了,相當於清理工作,關注兩個地方,1. onStop()這個方法是個抽象方法,需要子類去實現,也就是LayoutManager的子類,主要是做一個清理工作。2.mLayoutManager.onSmoothScrollerStopped(this)這個方法內部就是將LayoutManager的mSmoothScroller變數設定為null,其實也是清理工作。
再回到setScrollState方法,接下來就是跑dispatchOnScrollStateChanged(state),程式碼如下

    void dispatchOnScrollStateChanged(int state) {
        // Let the LayoutManager go first; this allows it to bring any properties into
        // a consistent state before the RecyclerView subclass responds.
        if (mLayout != null) {
            mLayout.onScrollStateChanged(state);
        }

        // Let the RecyclerView subclass handle this event next; any LayoutManager property
        // changes will be reflected by this time.
        onScrollStateChanged(state);

        // Listeners go last. All other internal state is consistent by this point.
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(this, state);
        }
        if (mScrollListeners != null) {
            for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                mScrollListeners.get(i).onScrollStateChanged(this, state);
            }
        }
    }

就是呼叫各種監聽scroll狀態的listener,順序如下

  1. LayoutManager的onScrollStateChanged
  2. RecyclerView子類的onScrollStateChanged
  3. 通過RecyclerView的setOnScrollListener設定的listener(此方法已經廢棄)
  4. 通過RecyclerView的addOnScrollListener方法設定的listener
    到這裡,setScrollState方法就跑完了,我們再回到stopScroll()方法
     public void stopScroll() {
        setScrollState(SCROLL_STATE_IDLE);
        stopScrollersInternal();
    }

可以看到又一次呼叫了stopScrollersInternal(),日了狗了,既然這裡肯定會呼叫一次,為什麼再setScrollState還要呼叫一次?這樣應該會在某些情況下導致scrollListener被呼叫兩次吧,不管了,繼續往下。
在呼叫完stopScroll()後,setLayoutManager方法接下來呼叫以下程式碼

        if (mLayout != null) {
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
            mRecycler.clear();

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();
        }

首先會判斷mLayout是不是null,這個mLayout指的是舊的Layoutmanager,我們就當做他不是null,正常情況下第一次執行肯定為null,但這裡我們從學習的目的出發,還是要知道,在有舊的Layoutmanager的時候,怎麼處理。
首先是呼叫mItemAnimator.endAnimations(),此方法的作用是立即結束所有item的動畫,並將相關property的值直接設定到結束值,ItemAnimator是個抽象類,關於ItemAnimator的解析可以參考如下文章RecylerView原始碼解析之ItemAnimator

接下來是呼叫mLayout.removeAndRecycleAllViews(mRecycler)方法,這個方法最終會調到如下方法

 public void removeAndRecycleViewAt(int index, Recycler recycler) {
     final View view = getChildAt(index);
     removeViewAt(index);
     recycler.recycleView(view);
 }

其中,removeViewAt(index)最終會調到RecyclerView的removeViewAt(index)方法,也就是ViewGroup的removeViewAt方法,這裡就不再分析了。我們主要看一下recycler.recycleView(view)方法,原始碼如下

 public void recycleView(View view) {
            // This public recycle method tries to make view recycle-able since layout manager
            // intended to recycle this view (e.g. even if it is in scrap or change cache)
            ViewHolder holder = getChildViewHolderInt(view);
            if (holder.isTmpDetached()) {
                removeDetachedView(view, false);
            }
            if (holder.isScrap()) {
                holder.unScrap();
            } else if (holder.wasReturnedFromScrap()) {
                holder.clearReturnedFromScrapFlag();
            }
            recycleViewHolderInternal(holder);
        }

首先通過isTmpDetached方法判斷view是否detached,其實內部就是判斷viewholder的mFlags有沒有設定FLAG_TMP_DETACHED這個標誌為,如果設定了,說明當前view已經被detach,我們這裡假設返回true,則進到removeDetachedView方法,該方法如下:

    @Override
    protected void removeDetachedView(View child, boolean animate) {
        ViewHolder vh = getChildViewHolderInt(child);
        if (vh != null) {
            if (vh.isTmpDetached()) {
                vh.clearTmpDetachFlag();
            } else if (!vh.shouldIgnore()) {
                throw new IllegalArgumentException("Called removeDetachedView with a view which"
                        + " is not flagged as tmp detached." + vh + exceptionLabel());
            }
        }

        // Clear any android.view.animation.Animation that may prevent the item from
        // detaching when being removed. If a child is re-added before the
        // lazy detach occurs, it will receive invalid attach/detach sequencing.
        child.clearAnimation();

        dispatchChildDetached(child);
        super.removeDetachedView(child, animate);
    }

該方法是ViewGroup的方法,RecyclerView有重寫這個方法,主要完成的事情是:

  • 復位了mFlags中FLAG_TMP_DETACHED
  • 清空當前child view的動畫
  • 分發dispatchChildDetached事件,分別會呼叫RecyclerView的onChildDetachedFromWindow方法,Adapter的onViewDetachedFromWindow方法和通過RecyclerView的addOnChildAttachStateChangeListener新增的listener。
  • 呼叫ViewGroup的removeDetachedView方法。

接下里會判斷當前child view是不是scrap的,如果是就將它設為非scrap,如果不是,則判斷是不是wasReturnedFromScrap,如果是,則清空FLAG_RETURNED_FROM_SCRAP標誌位,這裡先不講這些標誌是如何設定的。

然後程式碼會走到recycleViewHolderInternal(holder)

void recycleViewHolderInternal(ViewHolder holder) {
...
 final boolean transientStatePreventsRecycling = holder
                    .doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
                    && transientStatePreventsRecycling
                    && mAdapter.onFailedToRecycleView(holder);
        if (forceRecycle || holder.isRecyclable()) {
            if (mViewCacheMax > 0
                    && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                    | ViewHolder.FLAG_REMOVED
                    | ViewHolder.FLAG_UPDATE
                    | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                // Retire oldest cached view
                int cachedViewSize = mCachedViews.size();
                if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                    recycleCachedViewAt(0);
                    cachedViewSize--;
                }

                int targetCacheIndex = cachedViewSize;
                if (ALLOW_THREAD_GAP_WORK
                        && cachedViewSize > 0
                        && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                    // when adding the view, skip past most recently prefetched views
                    int cacheIndex = cachedViewSize - 1;
                    while (cacheIndex >= 0) {
                        int cachedPos = mCachedViews.get(cacheIndex).mPosition;
                        if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                            break;
                        }
                        cacheIndex--;
                    }
                    targetCacheIndex = cacheIndex + 1;
                }
                mCachedViews.add(targetCacheIndex, holder);
                cached = true;
            }
            if (!cached) {
                addViewHolderToRecycledViewPool(holder, true);
                recycled = true;
            }
        } else {
            // NOTE: A view can fail to be recycled when it is scrolled off while an animation
            // runs. In this case, the item is eventually recycled by
            // ItemAnimatorRestoreListener#onAnimationFinished.

            // TODO: consider cancelling an animation when an item is removed scrollBy,
            // to return it to the pool faster
            if (DEBUG) {
                Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                        + "re-visit here. We are still removing it from animation lists"
                        + exceptionLabel());
            }
        }
        // even if the holder is not removed, we still call this method so that it is removed
        // from view holder lists.
        mViewInfoStore.removeViewHolder(holder);
        if (!cached && !recycled && transientStatePreventsRecycling) {
            holder.mOwnerRecyclerView = null;
        }
    }

直接從重點部分開始,先通過if (forceRecycle || holder.isRecyclable())判斷當前view是否可回收,這裡的forceRecycle預設是false,所以只要看holder.isRecyclable()的值, 我們這裡先把它看做可回收,至於具體什麼情況下可回收,後面的部落格再講。接下來再判斷mViewCacheMax是否大於0,預設情況下這個值是2,這裡說明一下,那些被回收的View會被放到一個RecycledViewPool中,這個pool中的view是可以被其他RecyclerView的例項使用的,而把view放進這個pool的前提是mCachedViews這個list已經被塞滿,否則會先放到這個list中,而這個list的容量是mViewCacheMax決定的。再回到程式碼,當mViewCacheMax大於0且
holderflag由不包含ViewHolder.FLAG_INVALID|ViewHolder.FLAG_REMOVED |ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN這些值的時候,就可以開始回收工作了。
首先會判斷mCachedViews有沒有被塞滿,如果滿的話,就將第一個view放進pool中,並將第一個view中mCachedViews中移除。這樣mCachedViews就空出了一個位置,可以把我們要cache的view放進去,接下來要跑的這一段先不講,因為涉及到預抓取,後面的部落格再講。這段程式碼的作用就是決定view要插入到mCachedViews的哪個位置,反正預設是插入到尾部,我們這裡就當做它插入到了尾部。

   if (ALLOW_THREAD_GAP_WORK
           && cachedViewSize > 0
           && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
       // when adding the view, skip past most recently prefetched views
       int cacheIndex = cachedViewSize - 1;
       while (cacheIndex >= 0) {
           int cachedPos = mCachedViews.get(cacheIndex).mPosition;
           if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
               break;
           }
           cacheIndex--;
       }
       targetCacheIndex = cacheIndex + 1;
   }

接下來會判斷是否有cache成功,如果沒有cache成功,則直接放到pool中

 if (!cached) {
     addViewHolderToRecycledViewPool(holder, true);
     recycled = true;
 }

沒有cache的情況就是剛開始的這段返回了false。

  if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN))

最後呼叫removeViewHolder(),將此holder從mOldChangedHoldersmLayoutHolderMap中刪除,如果前面cache和recycle都失敗,且transientStatePreventsRecyclingtrue,則將holder.mOwnerRecyclerView設為null,表示沒有回收成功,transientStatePreventsRecycling這個變數是通過holder.doesTransientStatePreventRecycling()確定的,原始碼如下

        /**
         * @return True if ViewHolder is not referenced by RecyclerView animations but has
         * transient state which will prevent it from being recycled.
         */
        private boolean doesTransientStatePreventRecycling() {
            return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && ViewCompat.hasTransientState(itemView);
        }

從註釋可以看出其實就是判斷itemview是不是hasTransientState,關於TransientState我也不清楚,應該是表示有動畫正在進行,但這個動畫又不是ItemAnimation指定的動畫,先放著。

到這裡setLayoutManager方法中的mLayout.removeAndRecycleAllViews(mRecycler)就講完了,接下來是mLayout.removeAndRecycleScrapInt(mRecycler),這裡就不再分析了,因為邏輯很簡單,和上面的removeAndRecycleAllViews邏輯差不多。同學們可以自己去看。

再回到setLayoutManagre,接下來就是呼叫mRecycler.clear(),程式碼如下

        public void clear() {
            mAttachedScrap.clear();
            recycleAndClearCachedViews();
        }

就是把mAttachedScrap清空,這個mAttachedScrap就是存放scrapview的地方,然後再把mCachedViews中的view放到pool中,最後清空mCachedViews,就是這麼簡單。setLayoutManager接下來來呼叫的程式碼也很簡單,就是把新的LayoutManager賦值給mLayout,並將RecyclerView的引用賦值給新的LayoutManager,並更新·mCacheViews·的size, 我們這邊重點來看一下mRecycler.updateViewCacheSize()方法。

 void updateViewCacheSize() {
     int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
     mViewCacheMax = mRequestedCacheMax + extraCache;

     // first, try the views that can be recycled
     for (int i = mCachedViews.size() - 1;
             i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
         recycleCachedViewAt(i);
     }
 }

可以看到mCachedViews的最大size由mViewCacheMax決定,而mViewCacheMax的size則由mRequestedCacheMaxmLayout.mPrefetchMaxCountObserved兩者決定mRequestedCacheMax可以通過RecyclerView的setItemViewCacheSize方法設定,預設值是2,而mLayout.mPrefetchMaxCountObserved是由GapWorker決定,暫時先不講,因為我自己也不知道。。。哈哈。updateViewCacheSize()中,如果發現新的maxsize小於舊的size,則會把多出的那幾個cacheview放進pool中。

最後setLayoutManager方法呼叫requestLayout方法,進行重新佈局。

好了,setLayoutManager方法就分析到這裡,裡面有些地方我暫時還不清楚,比如GapWorker,等後面學習的深入再回來補上。也有可能有講的不對的地方,還請同學們提出。