1.ListView第一次加載
時(shí)序圖:
https://www.processon.com/view/link/5bd7b047e4b0fef7882c2fda
第一次加載時(shí),因?yàn)闆](méi)有緩存view,所以通過(guò)adapter的getItem來(lái)獲得要加載的view咬摇,ScrapView緩存為空罪郊,所以convertView為空反璃。
2.第二次Layout
即使是一個(gè)再簡(jiǎn)單的View取董,在展示到界面上之前都會(huì)經(jīng)歷至少兩次onMeasure()和兩次onLayout()的過(guò)程盐茎,這就意味著layoutChildren()過(guò)程會(huì)執(zhí)行兩次者冤。
與第二次不同的是肤视,ListView已經(jīng)有第一次加載的緩存,直接讀取緩存加載界面涉枫。
時(shí)序圖:
https://www.processon.com/view/link/5bd7d149e4b056d0cf9004c6
@Override
protected void layoutChildren() {
...
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
...
//選擇加載view方案
switch (mLayoutMode) {
...
default:
if (childCount == 0) {
if (!mStackFromBottom) {
final int position = lookForSelectablePosition(0, true);
setSelectedPositionInt(position);
sel = fillFromTop(childrenTop);
} else {
final int position = lookForSelectablePosition(mItemCount - 1, false);
setSelectedPositionInt(position);
sel = fillUp(mItemCount - 1, childrenBottom);
}
} else {
if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
sel = fillSpecific(mSelectedPosition,
oldSel == null ? childrenTop : oldSel.getTop());
} else if (mFirstPosition < mItemCount) {
sel = fillSpecific(mFirstPosition,
oldFirst == null ? childrenTop : oldFirst.getTop());
} else {
sel = fillSpecific(0, childrenTop);
}
}
break;
}
}
// 將所有沒(méi)有被重用到的view從mActiveViews轉(zhuǎn)移到mScrapViews中去
recycleBin.scrapActiveViews();
}
- 當(dāng)數(shù)據(jù)沒(méi)有變化時(shí)邢滑,調(diào)用fillActiveViews用來(lái)緩存所有的子View,然后調(diào)用detachAllViewsFromParent來(lái)清空view愿汰,在后面調(diào)用makeAndAddView時(shí)調(diào)用mRecycler.getActiveView(position)來(lái)獲取緩存困后。如果mActiveViews還有剩余的view沒(méi)有復(fù)用,最后都移到ScrapViews中去衬廷。
- 當(dāng)數(shù)據(jù)變化時(shí)摇予,調(diào)用recycleBin.addScrapView(getChildAt(i), firstPosition+i);將view都存在mCurrentScrap中(mViewTypeCount默認(rèn)為1,默認(rèn)存mCurrentScrap吗跋,對(duì)不同的mViewTypeCount有mScrapViews數(shù)組存儲(chǔ))
在addScrapView方法中侧戴,如果view正在執(zhí)行動(dòng)畫(huà)操作,設(shè)定setHasTransientView(true),將把view添加入mTransientStateViews中救鲤。在obtainView中會(huì)重新復(fù)用久窟。
- 第一次加載沒(méi)有數(shù)據(jù)所以忽略。
在選擇選擇加載view方案時(shí)
- 在第一次調(diào)用加載方法時(shí)本缠,因childCount == 0且不位于底部斥扛,走的fillFromTop方法。
- 第二次調(diào)用方法時(shí)丹锹,childCount大于0稀颁,根據(jù)點(diǎn)擊的item位置和顯示的第一個(gè)item位置來(lái)構(gòu)造加載view的位置。
- 最終都是循環(huán)調(diào)用makeAndAddView來(lái)加載單個(gè)view來(lái)顯示界面楣黍。復(fù)用view中匾灶,getActiveView優(yōu)先最高,其次是obtainView中的transientView租漂,最后是scrapView阶女。
3.滾動(dòng)加載view
onTouchEvent ——》 onTouchMove -》 scrollIfNeeded - 》 trackMotionScroll -》 fillGap -》 fillDown/fillUp -》 makeAndAddView - 》 obtainView -》 getScrapView
這里關(guān)注點(diǎn)主要在界面復(fù)用和加載上。
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
if (down) { //判斷是否向下滾動(dòng)
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
} else {
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
} else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
// The view will be rebound to new data, clear any
// system-managed transient state.
child.clearAccessibilityFocus();
mRecycler.addScrapView(child, position);
}
}
}
}
if (count > 0) {
detachViewsFromParent(start, count);
mRecycler.removeSkippedScrap();
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
}
- 如果是下劃哩治,頂部的item會(huì)先滑出屏幕秃踩,循環(huán)已加載的view判斷底部是否移出的屏幕,移出了就調(diào)用mRecycler.addScrapView加入緩存中业筏。
- 同理憔杨,上劃屏幕,底部的item如果移出的屏幕也加入緩存蒜胖。
- 將移出屏幕的view調(diào)用detachViewsFromParent移出消别,并清空SkippedScrap。
- fillGap調(diào)用條件是台谢,有新的view移入屏幕寻狂,需要加載了。fillGap就是實(shí)現(xiàn)新view加載的方法对碌。而fillGap里根據(jù)滑動(dòng)方向分別調(diào)用了fillDown和fillUp荆虱,這個(gè)我們?cè)诘诙蝜ayout已經(jīng)很熟悉了。
View obtainView(int position, boolean[] isScrap) {
...
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
...
}
與第二次layout不同的是朽们,這是滾動(dòng)中加載view怀读,ActiveView為空,而ScrapView有緩存骑脱,所以調(diào)用Adapter.getView方法菜枷,ScrapView的緩存view作為convertView參數(shù)傳入進(jìn)去。
4. notifyDataSetChanged
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
DataSetObservable.java
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
mDataSetObservable是在AbsListView中的AdapterDataSetObserver類(lèi)中實(shí)現(xiàn)叁丧,它繼承自AdapterView<ListAdapter>.AdapterDataSetObserver
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
最后一行啤誊,調(diào)用requestLayout()岳瞭,onLayout要被調(diào)用,開(kāi)始重新布局蚊锹。
5.總結(jié)
- 作為AbsListView的內(nèi)部類(lèi)RecycleBin瞳筏,view重用主要靠它實(shí)現(xiàn)。
- mActiveViews在數(shù)據(jù)沒(méi)有改變而進(jìn)行重繪時(shí)保存(例如多次重繪牡昆;調(diào)用notifyDataSetChanged而數(shù)據(jù)沒(méi)有改變等)姚炕,不用多次調(diào)用adapter的getView;
調(diào)用RecycleBin的fillActiveViews將view緩存到mActiveViews丢烘。
- mScrapViews/mCurrentScrap來(lái)緩存已經(jīng)移除屏幕的view(例如listview滾動(dòng)柱宦;數(shù)據(jù)有改變時(shí)調(diào)用notifyDataSetChanged),作為adapter的getView中convertView的參數(shù)播瞳;
RecycleBin的addScrapView或直接mRecycler.addScrapView加入緩存
- mTransientStateViews/mTransientStateViewsById來(lái)緩存處于transient的view(例如動(dòng)畫(huà)未執(zhí)行結(jié)束掸刊,hasTransientState為true,設(shè)置了setHasTransientView(true)赢乓,而沒(méi)有設(shè)置false)忧侧。
- 三者的優(yōu)先級(jí)依次,mActiveViews在makeAndAddView中調(diào)用牌芋,后面兩個(gè)在obtainView方法中被調(diào)用苍柏。