Android中的RecyclerView原始碼分析
RecyclerView元件
- Adapter
提供資料以及資料變更通知 - LayoutManager
佈局管理,負責測繪、指定item位置、item回收、 - ItemAnimator
Item變更動畫
關鍵實現
1、ViewHolder複用
三層快取
第一層:Recycler中的mCachedViews
第二層:ViewCacheExtension 由開發者實現的快取策略,可通過setViewCacheExtension設定到RecyclerView中
第三層:RecycledViewPool中的mScrap 可在多個RecyclerView共享的View快取池
Scrapped View(廢棄的View):一個View雖然仍附屬在它的父檢視RecyclerView上,但是已經被標記為已使用或已遷移。這樣的View就叫做Scrapped View。
獲取一個View的過程
//RecyclerView.Recycler
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
//所有獲取View呼叫的入口
public View getViewForPosition(int position) {
//呼叫內部實現
//引數 position 獲取一個View的位置
// false 不進行預檢操作 dryrun
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
//引數檢查
...
//如果處於提前佈局狀態,嘗試從mChangedScrap列表尋找
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
//如果沒找到,則從mAttachedScrap列表中尋找
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
//如果沒找到,或者找到的ViewHolder和OffsetPosition不匹
//配,則重新計算OffsetPosition。並且根據stable ids在
//mAttachedScrap中尋找(如果mAdapter存在stableIds)。
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
//如果還找不到,則從mViewCacheExtension中尋找,
//前提是開發者為RecyclerView設定了ViewCacheExtension
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
....
....
//如果沒有設定ViewCacheExtension,肯定還是找不到的,
//那麼就會從RecycledViewPool中尋找。
//因為RecycledViewPool是多個RecyclerView共享的,如果找到合適的ViewHolder,要先更新需要顯示的資訊。
holder = getRecycledViewPool().getRecycledView(type);
...
...
//最後一層快取也找不到的話,只能新建了
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
//填充資料
mAdapter.bindViewHolder(holder, offsetPosition);
...
return holder.itemView;
}
2、資料變更通知(觀察者模式)
觀察者AdapterDataObserver,具體實現為RecyclerViewDataObserver,當資料來源發生變更時,及時響應介面變化
public static abstract class AdapterDataObserver {
public void onChanged() {
// Do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// fallback to onItemRangeChanged(positionStart, itemCount) if app
// does not override this method.
onItemRangeChanged(positionStart, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
// do nothing
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// do nothing
}
}
被觀察者AdapterDataObservable,內部持有觀察者AdapterDataObserver集合
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
// since onItemRangeInserted() is implemented by the app, it could do anything,
// including removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
// since onItemRangeRemoved() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
}
Adapter內部持有AdapterDataObservable物件。
當我們為RecyclerView設定Adapter時,會向Adapter註冊一個觀察者RecyclerViewDataObserver。
adapter.registerAdapterDataObserver(mObserver);
當資料變更時,呼叫notify**方法時,Adapter內部的被觀察者會遍歷通知已經註冊的觀察者的對應方法,這時介面就會響應變更。
3、多種佈局型別展示
基類LayoutManager
子類有LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager
測量入口:RecyclerView的onMeasure方法
//mAutoMeasure代表是否是由已經存在的LayoutManager去執行測繪工作。
//當使用自定義的LayoutManager時,需要設定此值為false
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//如果RecyclerView的測量模式為EXACTLY(準確模式)
//則不必依賴子檢視的尺寸來確定RecyclerView本身的高度
//子檢視的測量工作會延遲到onLayout方法中
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
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;
//此方法會真正執行LayoutManager的測量佈局操作
dispatchLayoutStep2();
.....
.....
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
...
...
//mLayout即RecyclerView設定的LayoutManager
mLayout.onLayoutChildren(mRecycler, mState);
...
...
}
//LayoutManager的這個方法是一個空實現,具體佈局操作由子類決定
//當自定義LayoutManager時,這是必須要實現的一個方法
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
//這裡以LinearLayoutManager為例,說明佈局過程
//通過註釋可以很好的理解測量和佈局演算法流程
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
...
...
//第一步,通過檢查子檢視和其他變數,找出錨點座標和item位置錨點
//第二步,從底部向起始位置填充
//第三步,從頂部向末尾位置填充
//第四步,滾動要滿足要求的位置,例如當從底部填充時
...
...
//當錨點計算完成後,就開始填充view
//LayoutManager的三個子類都有這個填充方法,但是不同的
//的佈局型別,此方法會有不同的實現
fill(recycler, mLayoutState, state, false);
4、Item變更動畫
5、其他
notifyDataSetChanged發生了什麼
1、為頁面中存在的ViewHolder和Recycler中mCachesView中快取的ViewHolder新增一個標誌:ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
2、檢查有沒有等待執行的更新操作(add/remove/update/move)。如果沒有的話,並且當前沒有處於佈局狀態,並且沒有設定禁止LayoutFrozen,呼叫requestLayout()方法重新佈局。
3、如果有等待執行的更新操作怎麼辦?
不允許重新整理
notifyItemInserted發生了什麼
1、RecyclerView把Item的插入、刪除、移動、變更抽象成了一個個命令UpdateOp,並在AdapterHelper中維護了一個命令池。當插入一條Item時,向更新的命令佇列中新增一個“ADD”命令。
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
2、然後會觸發更新處理器,來處理新的命令。
3、RecyclerView會啟動一個Runnable來處理等待執行的命令。
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
private final Runnable mUpdateChildViewsRunnable = new Runnable() {
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
但是我們在單步除錯的時候,發現條件isLayoutRequested()為true,所以這個Runnable什麼都沒幹,直接返回了。
????
既然isLayoutRequested()為true,說明接下來會重新佈局,可能執行更新的地方放在了layout中。
我們在dispatchLayout()方法中的第二步dispatchLayoutStep1();發現了processAdapterUpdatesAndSetAnimationFlags()方法;
該方法主要做了兩件事:第一,根據操作型別和數量更新Adapter中Item的位置(例,插入一個新的Item,原列表中Item的Position要依次加一)。第二,設定RecyclerView的狀態mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations為true。