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,等後面學習的深入再回來補上。也有可能有講的不對的地方,還請同學們提出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章