在阅读ListView源码之前,我们首先需要了解到ListView的缓存机制,也就是RecycleBin缓存,RecycleBin是属于AbsListView的内部类。这里看需要了解RecycleBin的一些成员变量和方法。
RecycleBin
//在mActiveViews中存储的第一个视图的位置
private int mFirstActivePosition;
//缓存当前屏幕内View
private View[] mActiveViews = new View[0];
//已Type为下标的项目视图集合数组
private ArrayList<View>[] mScrapViews;
//项目视图Type数量
private int mViewTypeCount;
//第一种Type的项目视图集合
private ArrayList<View> mCurrentScrap;
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
private LongSparseArray<View> mTransientStateViewsById;
下面属于RecycleBin中一些重要的方法:
fillActiveViews:
这方法接收两个参数,childCount需要存储的View的数量,以及firstActivePosition当前ListView中第一个可见Item的position,RecycleBin调用该方法来存储指定位置的ListView元素到mActiveViews 数组中。
getActiveView
该方法接受一个指定的position值,将该position换算成指定的下标从mActiveViews 数组中获取一个View,该方法与fillActiveViews方法相对应,值得注意的是,获取到指定小标值后,当前位置view置null。也就是说该值只能被复用一次,否则下一次获取就是null
addScrapView
该方法接收两个参数分别是View和position,用于缓存废弃的View ,例如上下滑动滑出屏幕,通过mCurrentScrap和mScrapViews来存储指定type的view。
getScrapView
与addScrapView方法相对应,从废弃缓存中取出一个View,并从相对应的Type集合中获取,从最初添加的child进行获取,然后从集合中remove,看一看出每一个集合中的View也只能复用一次,然后移除。
//记录当前缓存的screen内View,包括位置
void fillActiveViews(int childCount, int firstActivePosition) {
//若当前数组length<childCount,重新new一个childCount长度数组
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
activeViews[i] = child;
//记住当前View的位置(未知根据当前View在Adapter中所有Item的相对位置)
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
//获取指定位置的视图,获取之后从View数组中移除
View getActiveView(int position) {
int index = position - mFirstActivePosition;
//activeViews和mActiveViews引用的地址是相同的
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
//获取到view后,当前位置view置null
activeViews[index] = null;
return match;
}
return null;
}
根据当前View的Type和是否有瞬时状态来确定是否存储到mScrapViews中,这里代码过长因此省略了一些代码
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
lp.scrappedFromPosition = position;
final int viewType = lp.viewType;
//根据当前type判断当前scrap是否应该被回收
if (!shouldRecycleViewType(viewType)) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
//添加到mSkippedScrap中
getSkippedScrap().add(scrap);
}
return;
}
...
//根据scrap是否有暂时状态进行存储
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
...
} else {
clearScrapForRebind(scrap);
//根据指定Type添加到指定集合
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
//从废弃缓存中取出一个View,并从mScrapViews中删除
View getScrapView(int position) {
//根据位置获取当前child的type类型
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
//若只有这一种Type则直接从mCurrentScrap集合中获取child,并从集合中删除
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
//根据whichScrap 下表获取指定位置child,并从集合中删除
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
通过setViewTypeCount方法可以看出来对于每一种Type的项目视图都分别创建了一个废弃视图集合来进行存储child
并在该方法中进行一些初始化,赋值等。
//为每种类型的数据项启动一个RecycleBin机制
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
}
//创建了一个元素为ArrayList的数组
ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
//为每一种ViewType创建一个ArrayList<View>
for (int i = 0; i < viewTypeCount; i++) {
scrapViews[i] = new ArrayList<View>();
}
mViewTypeCount = viewTypeCount;
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
看这个方法时最好可以去看看forceLayout 和requestlayout,invalidate的区别
//强制所有的项目视图执行一次绘制包含onMeasure,onLayout,onDraw
public void markChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
//forceLayout强制重新执行onMeasure,onLayout,onDraw
scrap.get(i).forceLayout();
}
} else {
final int typeCount = mViewTypeCount;
for (int i = 0; i < typeCount; i++) {
final ArrayList<View> scrap = mScrapViews[i];
final int scrapCount = scrap.size();
for (int j = 0; j < scrapCount; j++) {
scrap.get(j).forceLayout();
}
}
}
...
}
RecycleBin还有一些其他的方法,但是只要把上面的五种方法看完了基本上就差不多,其余的方法也与之息息相关,很容易看懂,这里就不再叨唠了。
onLayout的两次调用
在这里onLayout会被调用两次,具体为什么会调用两次,这里给大家提供一篇博客写的比较详细的onLayout两次调用,作者写的比较详细,而且我也是看这篇文章的才知道的,尴尬了。
接下来看一下onLayout的第一次调用。至于为什么要从onLayout开始,其实大家可以看郭霖大神的Android ListView工作原理完全解析毫无疑问前辈写的就是好,我也是看着大神的博客一路走过来的。膜拜,下面就从第一次onLayout开始我的ListView源码阅读之旅。
第一次调用onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//默认会调用两次onLayout
mInLayout = true;
//child有适配器填充,因此第一次获取的count=0;
final int childCount = getChildCount();
//数据改变时,会调用 forceLayout方法强制子View重绘
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
//RecycleBin机制调用一下方法强制缓存的所有Type的集合元素重绘
mRecycler.markChildrenDirty();
}
layoutChildren();
mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
if (mFastScroll != null) {
mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
}
mInLayout = false;
}
值得注意的是getChildCount()方法获取的是当前ListView的子元素数量,在第一次获取时childCount =0,接下来调用layoutChildren()方法,该方法就是本次的重点,点进去发现该方法属于空类,因此是在子类中进行实现,接下来就看一看ListView的layoutChildren()方法。
@Override
protected void layoutChildren() {
...
try {
super.layoutChildren();
invalidate();
if (mAdapter == null) {
resetList();
invokeOnItemScrollListener();
return;
}
final int childrenTop = mListPadding.top;
final int childrenBottom = mBottom - mTop - mListPadding.bottom;
//获取当前childcount:第一次调用为0,第二次开始有值
final int childCount = getChildCount();
//正常情况下mLayoutMode=LAYOUT_NORMAL,所以我们只需要看default
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
...
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
//数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
/**
* 第一次没有缓存childCount=0,从第二次开始缓存
*
* 数据没有改变,通过RecycleBin机制将所有的View缓存
*/
recycleBin.fillActiveViews(childCount, firstPosition);
}
//清除所有的View
detachAllViewsFromParent();
//recycleBin删除所有不存储在废弃View视图中的child
recycleBin.removeSkippedScrap();
switch (mLayoutMode) {
...
default:
/**
* 第一次onLayout,childcount=0,默认布局是从上往下
*
* 第二次开始,childcount开始>0
*
* mStackFromBottom:false表示从顶部向下,true表示从底部向上
*/
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;
}
/**
*刷新上面没有被重用的缓存视图
*
*调用scrapActiveViews方法将mActiveViews中存储的View缓存到对应的Type类型的集合中
*
*注意每种Type的集合打大小不得超过mActiveViews的大小
*
*/
recycleBin.scrapActiveViews();
...
}
因为代码比较长,所以我省略一部分非核心代码,接下来就从上往下进行分析
if (mAdapter == null) {
resetList();
invokeOnItemScrollListener();
return;
}
如果没有设置mAdapter ,则直接调用resetList()方法清除所有东西,直接return结束该方法。
final int childrenTop = mListPadding.top;
final int childrenBottom = mBottom - mTop - mListPadding.bottom;
//获取当前childcount:第一次调用为0,第二次开始有值
final int childCount = getChildCount();
因为时第一次调用onLayout方法,此时childcount==0,childrenTop 和childrenBottom 分别为ListView的可用空间的的Top和Bottom值。
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
数据发生改变时调用该方法,里面的代码还是很长的,有时间可以看一下。
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
//数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
/**
* 第一次没有缓存childCount=0,从第二次开始缓存
*
* 数据没有改变,通过RecycleBin机制将所有的View缓存
*/
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
//清除所有的View
detachAllViewsFromParent();
//recycleBin删除所有不存储在废弃View视图中的child
recycleBin.removeSkippedScrap();
获取RecycleBin 对象,因为是第一次onLayout,childCount==0.因此fillActiveViews方法没有起到作用,ListView到目前为止依然是空的,因此detachAllViewsFromParent也没有起到什么作用,接下来正常情况下mLayoutMode=LAYOUT_NORMAL,所以我们只需要看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);
}
}
此时childCount ==0, mStackFromBottom:false表示从顶部向下,true表示从底部向上,默认情况下mStackFromBottom==false。此时可以看到调用fillFromTop方法,反之调用fillUp方法。
/**
* nextTop表示ListView d的paddingTop有效的top距离
*
* mSelectedPosition默认为-1
*/
private View fillFromTop(int nextTop) {
mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
if (mFirstPosition < 0) {
mFirstPosition = 0;
}
//fillDown表示自顶向下填充ListView
return fillDown(mFirstPosition, nextTop);
}
fillFromTop方法内部调用的却是fillDown方法,可以看到两个方法fillDown和fillUp是相对应的,一个从顶部到底部,一个从底部到顶部。在这里就可以明白ListView的填充操作,在第一次调用onLayout时,就是通过fillUp和fillDown方法来实现的,这里我们来看一下fillDown方法
/**
* pos 默认情况下=0
*
* nextTop代表ListView有效的顶部距离Top
*
* mItemCount:代表的是Adapter中数据的数量,而不是ListView中的数据数量
*
* getChildCount:代表的是ListView包含的子元素个数
*/
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
/**
*end是ListView底部减去顶部的像素值,mItemCount是Adapter中元素数量
*刚开始nextTop一定小于end,且POS也小于mItemCount
*循环一下nextTop增加,POS+1,当nextTop>end时表示超出屏幕
* 若pos>mItemCount,表示Adapter中元素已经完全遍历,跳出循环
*/
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
//循环一次高度增加:bottom+分隔符高度
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
//元素+1.向下遍历
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
注释写的比较清楚,首先计算出了ListView的实际可用高度,通过判断nextTop < end && pos < mItemCount来确定是否超出屏幕,以及跳出循环。这里可以看出来ListView每次只加载一屏幕的子View。while 循环内部调用makeAndAddView方法,可以猜测内部实现了创建一个项目视图并填充ListView。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
//ListView 初始化时,mRecycler中缓存的View为null
if (!mDataChanged) {
//第二次获取View是从缓存中读取
final View activeView = mRecycler.getActiveView(position);
/**
* 在拉youtchild中第二次调用了recycleBin.fillActiveViews(childCount, firstPosition)缓存了
* 屏幕显示的所有View,因此直接读取缓存复用,调用setupChild方法填充ListView
*/
if (activeView != null) {
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
//返回一个当前位置的View,有缓存重用缓存View,没有则调用adapter的getView方法返回一个
final View child = obtainView(position, mIsScrap);
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
可以看到首先通过mRecycler读取缓存在mActiveViews数组中的元素,因为第一次调用mActiveViews中数组值为null,因此调用obtainView(position, mIsScrap)方法来获取一个元素,可以看出该方法非常重要啊!因为该方法位于父类AbsListView中,点击去查看。
View obtainView(int position, boolean[] outMetadata) {
...
//从废弃的缓存View列表中获取一个View,mRecycler会根据position获取Type来获取View
final View scrapView = mRecycler.getScrapView(position);
//调用Adapter的getView方法获取一个子View
/**
* 因为第一次调用时mRecycler钟缓存的废弃view==null,所以直接调用mAdapter.getView方法返回一个View
*
* 这里可以看出在我们自定义适配器时要判断convertView是否为null,
*/
final View child = mAdapter.getView(position, scrapView, this);
//列表滑出屏幕时,判断是否将scrapView添加进废弃View集合
if (scrapView != null) {
if (child != scrapView) {
//view不匹配,重新缓存到废弃View集合中
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
child.dispatchFinishTemporaryDetach();
}
}
...
return child;
}
obtainView方法中首先从废弃缓存中根据位置获取复用View,因为我们是第一次调用,此时该方法返回空值,此时调用 mAdapter.getView方法来返回一个子元素,这个方法大家就熟悉了,估计也明白了为什么自定义适配器的时候要判断convertView为什么要判断是否为null,因为第一次测量时获取的scrapView为null,接下来直接返回child。大家可以看下上面的makeAndAddView方法,获取View后,调用了setupChild方法,看来该方法就是最后用于填充View到ListView的方法。
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean isAttachedToWindow) {
...
//根据Adapter中传入的一些参数设置View的参数
p.viewType = mAdapter.getItemViewType(position);
p.isEnabled = mAdapter.isEnabled(position);
...
/**
* 添加一个新的Item,isAttachedToWindow=false调用addViewInLayout方法
*
* 因为在layoutChild中我们detach了所有的子View,防止数据错乱,此时服用一个detach的
*
* item,isAttachedToWindow=true,调用attachViewToParent方法
*/
if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter
&& p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
//向上滑动flowDown=false 向下滑动flowDown=true
//attachViewToParent中若index<0,则默认将child添加到底部
attachViewToParent(child, flowDown ? -1 : 0, p);
if (isAttachedToWindow
&& (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition)
!= position) {
child.jumpDrawablesToCurrentState();
}
} else {
p.forceAdd = false;
if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
p.recycledHeaderFooter = true;
}
//调用addViewInLayout方法将child添加进ListView中
addViewInLayout(child, flowDown ? -1 : 0, p, true);
child.resolveRtlPropertiesIfNeeded();
}
...
}
因为代码过长,所以我只保留了一部分主要代码,这里需要注意到一个参数isAttachedToWindow,当我们添加的View复用与RecycleBin的mActiveViews数组时isAttachedToWindow=true,复用自废弃缓存中时isAttachedToWindow=false,分别调用attachViewToParent和addViewInLayout将View填充到ListView中,在这里我们就可以想到了虽说ListView里面的数据可能有无数条,但每次只显示一屏幕的视图,滑动时又会复用缓存的视图,因此虽然ListView数据很多,但显示的其实就是那么几个View,才能在有限的内存下显示那么多条数据。第一次onLayout就结束了,接下来看第二次
第二次调用onLayout
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
/**
*数据发生改变时,通过RecycleBin机制将所有的废弃View添加进集合中
*/
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
/**
* 第一次没有缓存childCount=0,从第二次开始缓存
*
* 数据没有改变,通过RecycleBin机制将所有的View缓存
*/
recycleBin.fillActiveViews(childCount, firstPosition);
}
第二次调用layoutChildren方法,此时getChildCount()有数据,为一屏幕显示的视图个数,因此调用recycleBin.fillActiveViews来缓存当前屏幕上视图和位置。接下来看下面一句代码,我觉得很有必要解释一下
detachAllViewsFromParent();
调用该方法,来detach掉屏幕上的所有的View,防止数据的显示混乱,你们可能会说那样再次绘制数据不是消耗性能吗,这时就是RecycleBin缓存机制的作用体现了, 看到上面recycleBin.fillActiveViews(childCount, firstPosition)方法,此时已经将屏幕上的View缓存,在此展示时只需要再次调用缓存就行。
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;
因为childCount有值,大于0的话将调用fillSpecific方法,看一下该方法
/**
* 第二次调用onLayout,ListView包裹的子元素>0时会调用该方法
*
* position:选择的child,默认0
*
* top:当前选择的child的top,默认为listView的有效顶部top
*/
private View fillSpecific(int position, int top) {
boolean tempIsSelected = position == mSelectedPosition;
//优先加载指定位置的View,然后加载该子View往上和往下的View,默认情况下加载第一个View,然后往下加载
View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
// Possibly changed again in fillUp if we add rows above this one.
mFirstPosition = position;
View above; //上一个View
View below; //下一个View
final int dividerHeight = mDividerHeight;
//判断现先加载该View上面的View还是下面的
if (!mStackFromBottom) {
above = fillUp(position - 1, temp.getTop() - dividerHeight);
// This will correct for the top of the first view not touching the top of the list
adjustViewsUpOrDown();
below = fillDown(position + 1, temp.getBottom() + dividerHeight);
int childCount = getChildCount();
if (childCount > 0) {
correctTooHigh(childCount);
}
} else {
below = fillDown(position + 1, temp.getBottom() + dividerHeight);
// This will correct for the bottom of the last view not touching the bottom of the list
adjustViewsUpOrDown();
above = fillUp(position - 1, temp.getTop() - dividerHeight);
int childCount = getChildCount();
if (childCount > 0) {
correctTooLow(childCount);
}
}
if (tempIsSelected) {
return temp;
} else if (above != null) {
return above;
} else {
return below;
}
}
可以看到该方法首先加载指定位置的View,然后判断是加载在该View上面或者下面的View,又是分别调用fillUp和fillDown方法,接下来看一下内部调用的makeAndAddView方法
if (!mDataChanged) {
//第二次获取View是从缓存中读取
final View activeView = mRecycler.getActiveView(position);
/**
* 在拉youtchild中第二次调用了recycleBin.fillActiveViews(childCount, firstPosition)缓存了
* 屏幕显示的所有View,因此直接读取缓存复用,调用setupChild方法填充ListView
*/
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
此时属于第二次调用onLayout方法,此时mRecycler.getActiveView(position)获取的缓存View不为null,因此直接复用缓存View,与我们刚才讲的相对应,若为null的话,则继续调用obtainView方法,
//从废弃的缓存View列表中获取一个View,mRecycler会根据position获取Type来获取View
final View scrapView = mRecycler.getScrapView(position);
//调用Adapter的getView方法获取一个子View
/**
* 因为第一次调用时mRecycler钟缓存的废弃view==null,所以直接调用mAdapter.getView方法返回一个View
*
* 这里可以看出在我们自定义适配器时要判断convertView是否为null,
*
*
*/
final View child = mAdapter.getView(position, scrapView, this);
//列表滑出屏幕时,判断是否将scrapView添加进废弃View集合
if (scrapView != null) {
if (child != scrapView) {
//view不匹配,重新缓存到废弃View集合中
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
mRecycler.getScrapView(position)获取的废弃缓存若存在则复用,最后继续调用setupChild方法,还记得上面第一次调用时我们队addViewInLayout和attachViewToParent区别的讲解吗,简单来说就是添加一个新的子View则调用addViewInLayout方法,如果想复用一个detach掉的子View则调用attachViewToParent方法。关于ListView的两次调用,以及缓存的机制就先讲到这里了。下一次就是ListView的滑动策略了,下次再见!