OverView
RecyclerView是Android5.0推出的新組件皂岔,可以認(rèn)為是更加靈活強(qiáng)大的ListView考抄,在日常開發(fā)中基本上已經(jīng)取代了ListView成為長(zhǎng)列表控件的首選。
其繼承結(jié)構(gòu)如下:
從繼承結(jié)構(gòu)可以看到沐绒,RecyclerView是ViewGroup的直接子類俩莽,這里就是RecyclerView與ListView第一點(diǎn)大不同,ListView和ViewGroup之間隔了兩層乔遮,其實(shí)個(gè)人認(rèn)為在繼承結(jié)構(gòu)上揭露了RecyclerView相比ListView的優(yōu)點(diǎn):由于RecyclerView這么簡(jiǎn)單的繼承結(jié)構(gòu)扮超,說明了其主要功能都是通過組合來實(shí)現(xiàn)的,而ListView是通過繼承實(shí)現(xiàn)的蹋肮。
用法
通常來說出刷,在業(yè)務(wù)代碼中使用RecyclerView,主要有以下幾個(gè)步驟:
- 創(chuàng)建RecyclerView實(shí)例
- 為RecyclerView設(shè)置LayoutManager坯辩,LayoutManager是一個(gè)抽象類馁龟,常用的實(shí)現(xiàn)類有LinearLayoutManager
- 如有必要,為RecyclerView設(shè)置ItemAnimator
- 如有必要漆魔,為RecyclerView設(shè)置ItemDecoration
- 繼承RecyclerView.Adapter<VH extends ViewHolder>坷檩,實(shí)現(xiàn)getItemCount()/onCreateViewHolder(ViewGroup parent, int viewType)/onBindViewHolder(VH holder, int position)
- 在RecyclerView.Adapter中使用到的VH是ViewHolder的實(shí)現(xiàn)類,但ViewHolder雖然是一個(gè)抽象類改抡,其中卻沒有需要我們實(shí)現(xiàn)的抽象方法矢炼,ViewHolder中關(guān)鍵的成員是itemView,即其承載的視圖阿纤,可見性為public句灌,可以直接獲取
源碼淺析
這部分跟隨RecyclerView使用到的各個(gè)類的方法,看下這些關(guān)鍵方法是在何時(shí)被調(diào)用的
Adapter
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
在源碼中查找onCreateViewHolder(ViewGroup parent, int viewType)的調(diào)用阵赠,能得到下圖
內(nèi)一層是外一層的調(diào)用涯塔,最終可以看到主要的調(diào)用在LinearLayoutManager的onLayoutChildren()肌稻;直接的調(diào)用方法是在RecyclerView的tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs)中有這么1段:
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
簡(jiǎn)單來說,就是在layout()過程匕荸,如果holder為null爹谭,就調(diào)用這個(gè)方法來創(chuàng)建一個(gè)ViewHolder
- public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
首先在RecyclerView.Adapter的bindViewHolder(VH holder, int position)中有直接調(diào)用:
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
調(diào)用完之后從其itemView中取出LayoutParams來進(jìn)行處理
然后再源碼中查找最終的調(diào)用鏈,結(jié)果如下:
調(diào)用鏈中關(guān)鍵的一環(huán)是在RecyclerView的View getViewForPosition(int position, boolean dryRun)中
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
里面先調(diào)用了tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS)方法榛搔,在前面分析onCreateViewHolder()的時(shí)候已經(jīng)看到了诺凡,這個(gè)方法里嘗試獲取holder,取不到就創(chuàng)建holder践惑,創(chuàng)建后就進(jìn)行onBindViewHolder()的操作腹泌,bind完之后在這里可以看到,返回的是holder.itemView尔觉,因此在布局過程中凉袱,就是從這里獲得了正確的itemView
-
int getItemCount()
這個(gè)方法的調(diào)用,在源碼里看都是直接調(diào)用
int getItemViewType(int position)
這個(gè)方法在源碼中的被調(diào)用位置主要是RecyclerView的tryGetViewHolderForPositionByDeadline()函數(shù)侦铜,這個(gè)函數(shù)在上面也已經(jīng)出現(xiàn)過了
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
通過這個(gè)方法拿到的type专甩,就是在onCreateViewHolder()/onBindViewHolder()函數(shù)中的入?yún)ype
LayoutManager
- public abstract LayoutParams generateDefaultLayoutParams();
查找源碼庐橙,這個(gè)方法被調(diào)用的位置為RecyclerView的同名函數(shù)中:
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
if (mLayout == null) {
throw new IllegalStateException("RecyclerView has no LayoutManager");
}
return mLayout.generateDefaultLayoutParams();
}
- public void onLayoutChildren(Recycler recycler, State state)
這個(gè)方法本身并不是abstract函數(shù)乎赴,但在LayoutManager中必須重寫:
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
在源碼中查找其調(diào)用努溃,可以看到被調(diào)用的地方主要有兩處:
首先是RecyclerView的dispatchLayoutStep1()函數(shù)
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
其次是dispatchLayoutStep2()中:
private void dispatchLayoutStep2() {
eatRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
ItemDecoration
- onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
這個(gè)函數(shù)作用在于繪制分割線桐玻,在源碼中被調(diào)用的地方在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);
}
}
其實(shí)就是RecyclerView的onDraw()函數(shù)中進(jìn)行了分割線的繪制
-
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
這個(gè)函數(shù)是删窒,在每一項(xiàng)itemView繪制的時(shí)候宏多,通過控制outRect的left/top/right/bottom值萧求,來確定itemView四周留出來的范圍腕巡,效果類似于padding或margin
在源碼中被調(diào)用的地方有:
可以看到俊卤,主要是和measure有關(guān)的函數(shù)嫩挤,很容易理解,因?yàn)榫褪窃趍easure過程確定一個(gè)itemView占據(jù)多少空間
ItemAnimator
ItemAnimator主要用于對(duì)item的插入瘾蛋、修改俐镐、刪除動(dòng)畫處理主要有animateApprearance/animateDisappearance/animateChange/animatePersistence這幾個(gè)在對(duì)應(yīng)動(dòng)作的函數(shù)
-
public abstract boolean animateAppearance(@NonNull ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
在源碼中查找animateAppearance的調(diào)用鏈為:
最終又是在熟悉的dispatchLayoutStep3()中被調(diào)用
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
但這一行并不是很能看出怎么就調(diào)用了animateAppearance,看下再上一步的調(diào)用:
可以看到在調(diào)用process()函數(shù)的時(shí)候哺哼,是有根據(jù)record.flags來判斷應(yīng)該執(zhí)行的是哪個(gè)處理
-
public abstract boolean animateDisappearance(@NonNull ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo)
在源碼中查找其調(diào)用位置:
可以看到跟animateAppearance一樣佩抹,最終都是被dispatchLayoutStep3()調(diào)用
public abstract boolean animateChange(@NonNull ViewHolder oldHolder,
@NonNull ViewHolder newHolder,
@NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo)
這個(gè)函數(shù)的調(diào)用鏈和上述一致,就不重復(fù)貼圖了
public abstract boolean animatePersistence(@NonNull ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo);
這個(gè)函數(shù)的調(diào)用鏈依然和上述一致