RecyclerView 學習筆記

RecyclerView version: 26.1.0

GitHub

使用RecyclerView很久了,應該是從一出來就在使用吧,雖然大概的原理都懂,但是一直懶得看它的實現細節,最近想自己寫幾個LayoutManager,所以趁這個機會學習一下RecyclerView的源碼,了解它的實現細節,這樣寫起LayoutManager來也會更得心應手吧。

先來看看它有哪些主要的成員:

  • Recycler mRecycler // 緩存管理者,final類型-不允許擴展
  • LayoutManager mLayoutManager // 數據展示者
  • RecyclerViewDataObserver mObserver // 數據觀察者
  • Adapter mAdapter // 數據提供者
  • ItemAnimator mItemAnimator // 動畫類
  • ArrayList<ItemDecoration> mItemDecorations // 裝飾類

Recycler

Recycler是緩存管理者,它包含了幾個緩存容器,一直沒太明白mChangedScrap和mAttachedScrap的區別,或許區別不是特別大,誰要是知道了麻煩告訴我一聲

  • mChangedScrap:這里緩存的ViewHolder因為數據沒有變化,所以可以直接使用
  • mAttachedScrap:這里緩存的ViewHolder因為數據沒有變化,所以可以直接使用
  • mCachedViews:數據已經發生變化,重新使用需要調用Adapter.onBindViewHolder(...)
  • mViewCacheExtension:用戶自定義的緩存,通過RecyclerView.setViewCacheExtension(...),通過這個類,可以實現用戶自己管理RecyclerView的緩存
  • mRecyclerPool:獨立的ViewHolder緩沖池,可以多個RecyclerView共用,通過RecyclerView.setRecycledViewPool(...)設置
RecycledViewPool

RecyclerViewPool是一個Map,通過 <Int(Type),ScrapData> 這樣的格式存儲數據
也就是說,它和位置沒有關系,只在乎Item的Type類型

public static class RecycledViewPool {
    ...
    // RecycledViewPool很簡單,是一個緩沖池,
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    ...
}
 
Recycler的獲取ViewHolder的機制是這樣的:
getViewForPosition(int position) -> 
getViewForPosition(int position, boolean dryRun) -> 
tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs){
                        // 實際的代碼...
                }
  • 首先,從mChangedScrap中查找是否存在需要的ViewHolder,如果有則直接返回,否則
  • 從mAttachedScrap中查找是否存在需要的ViewHolder,否則
  • 從mCachedViews中查找是否存在需要的ViewHolder,否則
  • 檢查mViewCacheExtension是否不為空,如果是,從mViewCacheExtension中獲取ViewHolder,否則
  • 從RecyclerViewPool中獲取ViewHolder
  • 如果這個時候ViewHolder還是null,則從mAdapter.createViewHolder()創建一個ViewHolder
Recycler緩存ViewHolder的機制是這樣的:
  • 共有三種類型的緩存
  • A.通過mAttachedScrap或者mChangedScrap列表緩存
  • B.通過mCachedViews列表緩存
  • C.通過mRecyclerPool緩存

這里忽略了 mViewCacheExtension

這三種緩存方式的區別是:

  • A方式緩存的ViewHolder所包含的數據是有效的,如位置、View中的值等等
  • B方式緩存的ViewHolder數據已經是無效的,需要重新調用bindViewHolder進行數據綁定。
  • C方式緩存的ViewHolder是在B方式無法緩存的情況向才會緩存到C中,數據已經無效,需要重新調用bindViewHolder進行數據綁定,但是這種方式可以實現多個RecyclerView共同一個緩存池

Recycler對外分別提供了兩個方法進行數據的緩存:

  • 1:scrapView(View view) 根據一定的Policy將ViewHolder緩存到mAttachedScrap或者mChangedScrap
  • 2:recycleView(View view) 將ViewHolder緩存到mCachedViews中,如果緩存失敗了,則將ViewHolder緩存到mRecyclerPool中

LayoutManager

LayoutManager的主要工作是measure和position item views,以及根據一定的規則來確定是否回收不再對用戶可見item view

  • A:Child的measure,既尺寸和布局邊界的計算
  • B:Child的layout,既位置的擺放和移動
  • C:Child的回收管理,既何時回收,何時生成
  • D:Child何時add/attach和remove/detach到RecyclerView中
RecyclerView的measure

先來看看RecyclerView是如何計算自己的尺寸的

LayoutManager是RecyclerView的內部類,它沒有繼承任何的父類,所以,對于Android的View系統來說,LayoutManager是個什么玩意它根本不知道,RecyclerView才是屬于View樹中的一員,measure方法也只會傳遞到RecyclerView,那LayoutManager又是怎么處理Child的measure呢?

其實,原理特別簡單,就是RecyclerView把一部分measure的工作交給LayoutManager去處理,相當于將Child的measure工作交給了LayoutManager,具體的實現是,RecyclerView在自己的onMeasure(int widthSpec,int heightSpec)方法中調用LayoutManager的onLayoutChildren(Recycler recycler,State state),雖然原理聽起來很簡單,但是實現細節是很復雜,我簡單的梳理一下,刪除了很多細節:

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    ...
    if (mLayout.mAutoMeasure) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                && heightMode == MeasureSpec.EXACTLY;
        // 根據Mode判斷是否需要跳過measure,
        // 第一次進來的 mode 一般是MeasureSpec.UNSPECIFIED,
        // 既父類想知道RecyclerView想要多大空間,所以不會
        // 跳過measure過程
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        if (skipMeasure || mAdapter == null) {
            return;
        }
        // mState是一個狀態記錄的類,包含很多和RecyclerView
        // 狀態相關的屬性,由于onMeasure會執行多次,每次的measure
        // 做的工作可能不同,所以用 mLayoutStep
        // 記錄當前measure或者layout是那種類型,一共有三種
        // STEP_START:初次measure
        // STEP_LAYOUT:第二次measure
        // STEP_ANIMATIONS:處理動畫
        // 如果初次進來,調用相應的第一步對應的方法,下面
        // 分析了這個方法
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        // 執行第二步Layout
        dispatchLayoutStep2();

        // 經過第二次Measure之后,RecyclerView的Child自身尺寸已經
        // 計算出來了,所以我們可以根據Child的尺寸重新調整RecyclerView
        // 自己的尺寸了
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    } else {
        ...
        // 現在的LayoutManager一般都走得上面那個分支,
        // 既 mAutoMeasure=true,所以不介紹這個分支
    }
}


/**
 * 這個方法主要做下面幾件事情:
 * - 處理Adapter的刷新
 * - 計算動畫
 * - 保存當前View狀態
 * - 如果需要,執行預布局并保存這些信息
 */
private void dispatchLayoutStep1() {
    ...
    if (mState.mRunSimpleAnimations) {
        // 如果RecyclerView當前狀態是執行簡單動畫,比如滑動時候的動畫
        // 動畫這塊的處理后面會講到
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            ...
            // 通過ItemAnimator獲取當前狀態的一些信息,
            // 一般是些位置信息
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            // 將這些信息存到ViewInfoStore的mLayoutHolderMap中
            // 以后在正式動畫的時候,這些信息將作為動畫開始的狀態
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            ...
        }
    }
    // 這塊處理的是對Item執行添加、移除、調整位置的動畫
    if (mState.mRunPredictiveAnimations) {
        // 由于這些動畫一般都是Item的位置發生變化,所以要
        // 先保存Item原來的位置,遍歷所有的ViewHolder
        // 讓mOldPosition=mPosition
        saveOldPositions();
        final boolean didStructureChange = mState.mStructureChanged;
        mState.mStructureChanged = false;
        // 執行一次layout以便計算出ViewHolder最新的位置
        mLayout.onLayoutChildren(mRecycler, mState);
        mState.mStructureChanged = didStructureChange;

        for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
            final ViewHolder viewHolder = getChildViewHolderInt(child);
            // 獲取最新ViewHolder的位置和狀態信息
            final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
                    mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
            // 將這些信息保存到mViewInfoStore
            // 至此,ViewHolder的起始位置和終止位置都已經
            // 計算完成,只需要在合適的地方觸發動畫即可
            if (wasHidden) {
                recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
            } else {
                mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
            }
        }
        clearOldPositions();
    } else {
        clearOldPositions();
    }
    ...
    // 將mLayoutStep置為STEP_LAYOUT,靜候下一次onMeasure
    mState.mLayoutStep = State.STEP_LAYOUT;
}


private void dispatchLayoutStep2() {
    // 如果當前正在執行動畫,則直接結束動畫,將位置置為最終位置
    mAdapterHelper.consumeUpdatesInOnePass();
    // 再執行一次LayoutChildren
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    ...

    // 將mLayoutStep狀態置為STEP_ANIMATIONS
    mState.mLayoutStep = State.STEP_ANIMATIONS;
}

接著,RecyclerView的onLayout會執行,然后調用第三步Layout

private void dispatchLayoutStep3() {
    ...
    // 將mLayoutStep置為STEP_START
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) {
        ...
        // 執行動畫
        mViewInfoStore.process(mViewInfoProcessCallback);
    }
    
     // 回收垃圾
    mLayout.removeAndRecycleScrapInt(mRecycler);
    ...
    // 調用onLayoutCompleted
    // 到這里,一個measure輪回就算完成
    mLayout.onLayoutCompleted(mState);
    ...
}

通過上面的代碼,大概能知道onMeasure這一個過程,但是有幾點需要注意:

  • 1.onMeasure的職責是計算RecyclerView的尺寸,在它內部調用了dispachLayoutStep*()這三個方法也是為了通過Item的大小來確定RecyclerView的尺寸,
  • 2.dispachLayoutStep*()這幾個方法在其他的地方也會調用
    在列表滑動的時候,不會觸發onMeasure,onMeasure只會在

我們知道Android的View系統是樹形結構,不管是在布局還是事件傳遞都是從樹的根節點開始往枝葉傳遞,所以繪制的流程一般是這樣的:
measure過程:

ViewGroup.onMeasure() -> ViewGroup.onMeasure() -> View.onMeasure()

這個過程一般會執行兩遍,
舉個簡單的例子,地主家分地,首先,父親問所有的兒子,你想要多少畝地,爹就給你多少,然后兒子們就會很傻的告訴父親,我想要多少多少(其實沒有告訴,只是自己記下來,但是父親能看到),父親等兒子們計算完成后,默默的掏出算盤算了一下,然后發現,唉,兒子太多,這地不夠分呀,于是自己又定了個規則,大兒子最多給多少,二兒子給多少等等,這樣地才夠分,于是乎,兒子們又會在這個上限范圍內重新計算一次,算算也夠吃,就這樣吧。

所以onMeasure()一般最少會執行兩遍,等onMeasure執行完成后,父親就會根據每個兒子分的地尺寸,執行具體的分配(onLayout),如大兒子,你最大,給你山那頭最大的那一塊地吧,小兒子,你最可愛,給你那塊水地吧,等等,所以其實程序中的很多邏輯跟現實邏輯是相通的。

扯遠了,我覺得RecyclerView有意混淆onMeasure和onLayout的過程,在它內部,布局就是那三個Step,不管是在onMeasure調用還是onLayout調用、或者是其他地方調用,反正就這三步,按順序走完。

LayoutManagerd的onLayoutChildren

onLayoutChildren: 顧名思義,就是對RecyclerView中ChildView的管理,既Cell或者說是Item,它主要負責對ChildView在RecyclerView中的布局,如果直接看LinearLayoutManager這類已經寫好的LayoutManager太復雜,因為系統提供的LayoutManager需要考慮的東西特別多,所以我決定從頭寫一個,一點一點的完成一個最小但是可用的LayoutManager。

自定義一個LayoutManager需要你繼承一下LayoutManager:

public class MyLayoutManager extends RecyclerView.LayoutManager{}

由于LayoutManager是一個抽象類,有一個抽象方法是必須重寫的:

public class MyLayoutManager extends RecyclerView.LayoutManager {
    @Override
    public RecyclerView.LayoutParams generateDefaultLayoutParams() {
        return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
                RecyclerView.LayoutParams.WRAP_CONTENT);
    }
}

OK,這就是最小的自定義LayoutManager,然后你就可以高高興興的把它設置到RecyclerView中,點擊編譯運行,裝到手機上試一下,然后你就會發現什么也沒有,額,有才怪呢。

上面剛剛說了,LayoutManager的onLayoutChildren是負責ChildView的布局,由于我們沒有重寫這個方法,而父類也只是個空實現,對ChildView我們什么也沒有做,所以就什么都不會顯示嘍...

那我們看看怎么寫這個onLayoutChildren

// 先來看看這個方法的參數
// recycler:上面介紹過,負責ChildView的回收
// state:保存一些RecyclerView的狀態
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    fillViewport(recycler, state);
}

/**
 * 這個方法的作用就是將RecyclerView中用戶可見部分填滿
 */
void fillViewport(RecyclerView.Recycler recycler, RecyclerView.State state){
    // 當執行到這里呢,有可能RecyclerView一個ChildView都沒有
    // 也有可能已經有了,所以我們先獲取一下childCount
    int childCount = getChildCount();
    // 1.獲取第一個ChildView的信息,如果沒有,默認是0
    int firstChildTop = 0;
    int firstPosition = 0;
    if(childCount > 0){
        View firstChild = getChildAt(0);
        firstChildTop = getDecoratedTop(firstChild);
        firstPosition = getPosition(firstChild);
    }
    
    // 2.暫時回收所有的ChildView
    detachAndScrapAttachedViews(recycler);
    
    // 我們想實現LinearLayoutManager的效果,
    // 所以要把RecyclerView填滿,既讓它的ChildView
    // 從上往下填滿RecyclerView,所以我們從第一個
    // firstPosition開始往下排列ChildView
    
    // 3.準備開始布局,由于firstChild
    // 可能在整個RecyclerView的頂部,中間,甚至是底部
    // 所以從firstChild開始布局,有可能需要往上布局
    // 也有可能需要往下布局
    // 3.1 向下布局
    int nextPosition = firstPosition;
    int nextTop = firstChildTop;
    for (; nextTop < getHeight()
            && nextPosition >= 0
            && nextPosition < state.getItemCount(); nextPosition++) {
        // 從第一位置開始,一個Item一個Item的往下填滿RecyclerView
        View child = recycler.getViewForPosition(nextPosition);
        addView(child);
        // addView完成以后,調用下measure測量一下這個Child想要多大的空間
        measureChildWithMargins(child, 0, 0);
        // 用這個方法獲取measureHeight會把Decorated也加上,所以最好用
        // 這個方法獲取高度
        int itemMeasureHeight = getDecoratedMeasuredHeight(child);
        // 將ChildView放在對應的位置
        layoutDecoratedWithMargins(child, 0, nextTop, getWidth(), nextTop + itemMeasureHeight);
        // 記得累加一下nextTop的位置
        nextTop += itemMeasureHeight;
    }
    
    // 3.2 向上布局
    int prevPosition = firstPosition - 1;
    int preBottom = firstChildTop;
    for (; previewBottom >= 0
            && prevPosition >= 0
            && prevPosition < state.getItemCount(); prevPosition--) {
        // 從第一位置開始,一個Item一個Item的往上填滿RecyclerView
        View child = recycler.getViewForPosition(prevPosition);
        addView(child);
        // addView完成以后,調用下measure測量一下這個Child想要多大的空間
        measureChildWithMargins(child, 0, 0);
        // 用這個方法獲取measureHeight會把Decorated也加上,所以最好用
        // 這個方法獲取高度
        int itemMeasureHeight = getDecoratedMeasuredHeight(child);
        // 將ChildView放在對應的位置
        layoutDecoratedWithMargins(child, 0, preBottom - itemMeasureHeight, getWidth(), preBottom);
        // 記得累加一下preBottom的位置
        preBottom -= itemMeasureHeight;
    }
    
    // 4.清理對用戶不可見的View
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (getDecoratedBottom(child) < 0 || getDecoratedTop(child) > getHeight()) {
            removeAndRecycleView(child,recycler);
        }
    }
}

到這里,簡單的布局就完成了,運行一下看看也好像沒什么問題,但是就是不會滑動,如果要處理滑動,我們需要處理另外的一個方法:

// 首先,告訴RecyclerView,我支持上下滑動
@Override
public boolean canScrollVertically() {
    return true;
}

// 然后,在用戶上下滑動的時候,這個方法就會被執行
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    // 遍歷所有的ChildCount,移動它們的位置
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        if (child != null) {
            child.offsetTopAndBottom(-dy);
        }
    }
    // 滑動的時候,也要調用fillViewPort處理布局
    fillViewPort(recycler, state);
    return -dy;
}

至此呢,一個最最最簡陋的LayoutManager就算完工,如果需要一些復雜的效果,就需要你做一些自己的加工處理。

RecyclerViewDataObserver

我們知道RecyclerView的數據是來自Adapter的,而展示數據則是由LayoutManager負責,做動畫由ItemAnimator負責,那當數據源發生變化的時候,如何把這個信息告訴RecyclerView和各個組件呢?這個時候RecyclerViewDataObserver就派上用場了。

我們在向RecyclerView插入一條數據的時,一般都會調用Adapter的notifyItemInserted(),那我們就順著這條線,看看RecyclerView是如何傳遞這一個事件的:
首先,RecyclerViewDataObserver是RecyclerView的一個屬性,我們在調用RecyclerView.setAdapter()的時候,RecyclerView會把這個屬性注入到Adapter中去:

public void registerAdapterDataObserver(AdapterDataObserver observer) {
    mObservable.registerObserver(observer);
}

AdapterDataObservable這個類很簡單,就是做幾個簡單的封裝

也就是說,相對有Adapter來說,RecyclerView是觀察者,而Adapter是被觀察者。
當Adapter.notifyItemInserted()被調用時,流程大概是這樣的:

Adapter.notifyItemInserted() -> 
AdapterDataObservable.notifyItemInserted() -> 
RecyclerViewDataObserver.onItemRangeInserted() ->
// 這個方法會記錄刷新前Item的位置信息,動畫的真正執行在
// 上面介紹步驟的dispatchLayoutStep3()
AdapterHelper.onItemRangeInserted() ->
RecyclerViewDataObserver.triggerUpdateProcessor() -> 
RecyclerView.consumePendingUpdateOperations -> 
RecyclerView.dispatchLayout()

ItemAnimator

再來看一下RecyclerView動畫相關的東東

上面介紹過事件的傳遞,知道了插入一條數據后,AdapterHelper.onItemRangeInserted()方法會記錄動畫之前的位置信息,所以我們來看一下這個方法:

/**
 * RecyclerViewDataObserver.onItemRangeInserted
 */
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
    if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
        // 觸發更新
        triggerUpdateProcessor();
    }
}

/**
 * AdapterHelper.onItemRangeInserted
 */
boolean onItemRangeInserted(int positionStart, int itemCount) {
    if (itemCount < 1) {
        return false;
    }
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
    // 記錄一下當前需要執行的動畫類型,可能會有多個
    mExistingUpdateTypes |= UpdateOp.ADD;
    return mPendingUpdates.size() == 1;
}

/**
 * 這個方法就是用來記錄動畫前的狀態信息:位置、動畫類型、item數量
 * 將這些信息緩存到mPendingUpdates
 * 由于這里可能需要頻繁的創建和銷毀UpdateOp對象,所以用了一個對象池的概念
 */
@Override
public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
    UpdateOp op = mUpdateOpPool.acquire();
    if (op == null) {
        op = new UpdateOp(cmd, positionStart, itemCount, payload);
    } else {
        op.cmd = cmd;
        op.positionStart = positionStart;
        op.itemCount = itemCount;
        op.payload = payload;
    }
return op;
}

void triggerUpdateProcessor() {
    // 這里有兩個分支,如果調用RecyclerView.setHasFixedSize(),
    // 則會執行上面這個分支,否則走下面的,基本上沒啥區別,只是上面的
    // 多做了一些處理,但最后都會調用requestLayout();
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        // 通過post的好處是,這個動畫會在16秒這樣的垂直同步幀上執行
        // 具體可以查一下Android垂直同步
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

/**
 * 在上面好像有介紹這個方法,動畫相關的處理
 * 在 step1 和 step3 ,所以我們先看看第一步中動畫相關的部分
 */ 
void dispatchLayout() {
    ...
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}


private void dispatchLayoutStep1() {
    ...

    if (mState.mRunSimpleAnimations) {
        // 在布局前,先記錄View的位置
        int count = mChildHelper.getChildCount();
        for (int i = 0; i < count; ++i) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            ...
            // ItemAnimator是一個動畫執行類,所以先用它記錄下
            // 它需要的信息,每個ItemAnimator的子類都可以自己
            // 想記錄哪些信息
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPreLayoutInformation(mState, holder,
                            ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
                            holder.getUnmodifiedPayloads());
            // 將這些信息緩存到mViewInfoStore中
            // 注意這里是addToPreLayout
            mViewInfoStore.addToPreLayout(holder, animationInfo);
            ...
        }
    }
    ...
}

/**
 * 根據ViewHolder的狀態生成對應的動畫FLAG
 */
static int buildAdapterChangeFlagsForAnimations(ViewHolder viewHolder) {
    int flags = viewHolder.mFlags & (FLAG_INVALIDATED | FLAG_REMOVED | FLAG_CHANGED);
    if (viewHolder.isInvalid()) {
        return FLAG_INVALIDATED;
    }
    if ((flags & FLAG_INVALIDATED) == 0) {
        final int oldPos = viewHolder.getOldPosition();
        final int pos = viewHolder.getAdapterPosition();
        if (oldPos != NO_POSITION && pos != NO_POSITION && oldPos != pos) {
            flags |= FLAG_MOVED;
        }
    }
    return flags;
}


/**
 * 這一步則是在onLayoutChildren()之后執行的
 * 主要處理一些動畫信息以及善后工作
 */
private void dispatchLayoutStep3() {
    ...
    mState.mLayoutStep = State.STEP_START;
    if (mState.mRunSimpleAnimations) {
        for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
            ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
            long key = getChangedHolderKey(holder);
            // 獲取View現在的狀態,既layout之后的位置信息
            final ItemHolderInfo animationInfo = mItemAnimator
                    .recordPostLayoutInformation(mState, holder);
            // 將現在的位置信息存入mViewInfoStore
            // 注意這里是addToPostLayout
            mViewInfoStore.addToPostLayout(holder, animationInfo);
        }

        // 觸發動畫執行
        mViewInfoStore.process(mViewInfoProcessCallback);
    }

    // 一些善后工作
    ...
}

/**
 * 根據InfoRecord中flag的不同執行對應的動畫
 */
void process(ProcessCallback callback) {
    // 為什么要從列表的末尾開始執行呢,因為要執行:
    // mLayoutHolderMap.removeAt(index);
    // 所以最好從后面逆序遍歷
    for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
        final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index);
        final InfoRecord record = mLayoutHolderMap.removeAt(index);
        ...
        // 以Insert為例 
        else if ((record.flags & FLAG_POST) != 0) {
            // 
            callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
        } 
        ...
        InfoRecord.recycle(record);
    }
}

/**
 * RecyclerView.animateAppearance
 */
void animateAppearance(@NonNull ViewHolder itemHolder,
        @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
    // 動畫期間不可回收
    itemHolder.setIsRecyclable(false);
    if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
        postAnimationRunner();
    }
}

/**
 * SimpleItemAnimator
 */
@Override
public boolean animateAppearance(@NonNull ViewHolder viewHolder,
        @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
        // ...
        return animateAdd(viewHolder);
    }
}

/**
 * DefaultItemAnimator
 */
@Override
public boolean animateAdd(final ViewHolder holder) {
    resetAnimation(holder);
    // O__O "… 額,add的動畫就是把View透明的置為0?
    holder.itemView.setAlpha(0);
    // 由于可能同時執行的有很多個動畫,所以我們將下一幀執行
    // 前多有Add類型的動畫先暫存到mPendiingAdditions中
    // 同理還有mRemoveAnimations等
    mPendingAdditions.add(holder);
    return true;
}

/**
 * RecyclerView
 */
void postAnimationRunner() {
    // 緊接著馬上將動畫發射出去
    if (!mPostedAnimatorRunner && mIsAttached) {
        ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
        mPostedAnimatorRunner = true;
    }
}

/**
 * DefaultItemAnimator
 */
@Override
public void runPendingAnimations() {
    boolean removalsPending = !mPendingRemovals.isEmpty();
    boolean movesPending = !mPendingMoves.isEmpty();
    boolean changesPending = !mPendingChanges.isEmpty();
    boolean additionsPending = !mPendingAdditions.isEmpty();
    if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
        // nothing to animate
        return;
    }
    // First, remove stuff
    ...
    // Next, move stuff
    ...
    // Next, change stuff, to run in parallel with move animations
    ...
    // Next, add stuff
    // 還是以Add為例
    if (additionsPending) {
        final ArrayList<ViewHolder> additions = new ArrayList<>();
        additions.addAll(mPendingAdditions);
        mAdditionsList.add(additions);
        mPendingAdditions.clear();
        Runnable adder = new Runnable() {
            @Override
            public void run() {
                for (ViewHolder holder : additions) {
                        // 遍歷執行所有的Add動畫
                    animateAddImpl(holder);
                }
                additions.clear();
                mAdditionsList.remove(additions);
            }
        };
        ...
        adder.run();
    }
}

/**
 * DefaultItemAnimator
 * 正式執行add動畫
 */
void animateAddImpl(final ViewHolder holder) {
    final View view = holder.itemView;
    // 看到這里應該恍然大悟了把,其實RecyclerView的Item動畫
    // 只不過是在itemView上執行的屬性動畫,既然是這樣的,那我們
    // 是不是就可以在itemView上做些壞壞的事情呢?
    final ViewPropertyAnimator animation = view.animate();
    mAddAnimations.add(holder);
    animation.alpha(1).setDuration(getAddDuration())
            .setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animator) {
                    dispatchAddStarting(holder);
                }

                @Override
                public void onAnimationCancel(Animator animator) {
                    view.setAlpha(1);
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    animation.setListener(null);
                    dispatchAddFinished(holder);
                    mAddAnimations.remove(holder);
                    dispatchFinishedWhenDone();
                }
            }).start();
}

ItemDecoration

Decoration:裝飾,修飾

ItemDecoration是負責對RecyclerView進行裝飾的類,如添加分割線,調整間距等,
下面這個類就是ItemDecoration基類,這個類的前兩個方法是用來將畫一些東西在RecyclerView的上面或者下面,最后一個是調整Item的間距

/**
 * 這個類是負責實現ItemDecoration的,看起來很簡單
 */ 
public abstract static class ItemDecoration {
    
    /**
     * 在Item的下層繪制
     */
    public void onDraw(Canvas c, RecyclerView parent, State state) {
        
    }
    /**
     * 在Item的上層繪制
     */
    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        
    }
    
    /**
    * 計算Item上下左右間隙,這些間隙的信息在計算ChildView的位置的時候
    * 非常重要,所以我們上面在獲取ChildView高度的時候總是用
    * getDecoratedMeasuredXXX(),而不是直接調用View.getMeasureXXX()
    */
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        
    }
}


/**
 * RecyclerView
 */
@Override
public void draw(Canvas c) {
    super.draw(c);
    // super.draw() 會出發RecyclerView.onDraw(c)
    // 所以,下面的onDraw會先執行,然后執行這里的onDrawOver
    // onDrawOver是等所有的子View繪制完成后執行的,所以
    // 在onDrawOver方法執行的繪制會在View的上方
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    ...
}

/**
 * RecyclerView
 */
@Override
public void onDraw(Canvas c) {
    super.onDraw(c);
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDraw(c, this, mState);
    }
}

到此呢,RecyclerView就分析的差不多了,由于RecyclerView代碼實在是太多了,足足有一萬三千多行,所以其中的一些實現細節都沒有提,如果想要詳細認識RecyclerView,一定要結合源碼看,否則還是只能看個大概,另外,如果文中有什么錯誤,歡迎指正。

THE END

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容