RecyclerView version: 26.1.0
使用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,一定要結合源碼看,否則還是只能看個大概,另外,如果文中有什么錯誤,歡迎指正。