RecyclerView是Android 5.0系统官方推出的一个代替listView的组件,那么究竟好在哪里呢?我们来仔细分析一下这两个组件。
listView和RecycleView的原理大致相同,如下图
都是在内部维护一个缓存池,回收划出列表的item,添加给将要进入列表的item。只不过ListView内部是两级缓存,分别是mActiveViews和mScrapViews.而RecycleView内部有四级缓存。
listView的实现原理
首先需要介绍一下RecycleBin 这个类。
RecycleBin
RecycleBin中的变量
mRecyclerListener:
一般会在某个item被移除屏幕的时候调用,将这个item回收到scrap heap,并回收item上相关的资源布局
mActiveViews:
用来保存item的一个临时数组,主要是用在listView第二次onLayout()时,给ListView提供item,
mFirstActivePosition
存储在mActiveViews中的第一个View的位置
mViewTypeCount
view类型数量,
mScrapViews和mCurrentScrap
mCurrentScrap是ArrayList<View> 类型
而mScrapViews是ArrayList<View>[]类型
当mViewTypeCount=1,也就是listView中只有一种类型的item时(不包括header和footer),item是存入到mCurrentScrap中的
当mViewTypeCount>1的时候,ScrapViews数组就会有多个值,分别缓存一个类型的item
另外,adapter中getView()方法中的参数的convertView的值就是mScrapViews和mCurrentScrap传过去的。
RecycleBin中的方法
markChildrenDirty():该方法会为回收回来的每一个view调用forceLayout(),作用是在该view被复用的时候重新layout,
public void markChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
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();
}
}
}
}
/**
* Forces this view to be laid out during the next layout pass.
* This method does not call requestLayout() or forceLayout()
* on the parent.
*/
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
}
fillActiveViews():接受两个参数,第一个是要存储view的数量,一般会是一个屏幕所见的item数量,第二个参数表示屏幕中第一个可见item的position,这个方法被调用之后,mActiveViews这个数组就会缓存相应数量的item
void fillActiveViews(int childCount, int firstActivePosition) {
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();
// Don't put header or footer views into the scrap heap
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in
// active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child;
}
}
}
getActiveView(): 与fillActiveViews()相对应,获取相应位置的item,但是为什么在获取到对应的View之后,要将mActiveViews[]数组对应位置的数据置为空呢?那是因为mActiveViews[]这个数组实际上只是一个临时数组,方便listView第二次onLayout的时候提高效率,置为空就可以回收内存了。
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
addScrapView():首先判断是否需要回收,header和footer都是不回收的,加到忽略的list里,接着判断mViewTypeCount的数量,即item的类型有几种,mViewTypeCount==1,就加到mCurrentScrap中,>1,就加到mScrapViews[]中,
另mRecyclerListener.onMovedToScrapHeap(scrap);当item被回收的时候,通知监听器回收资源,这也是为什么listView能存放那么多数据而不崩溃的原因,
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) {
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
getSkippedScrap().add(scrap);
}
return;
}
.....
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
.......
} else {
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
getScrapView():复用item
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
retrieveFromScrap():根据position,查找对应的view,从代码来看分三种情况
1.如果view.scrappedFromPosition==position,直接返回view
这里的scrappedFromPosition就是在addScrapView回收时的位置。
2.不符合1中的条件,则会返回mScrapView中的最后一个数据
3.如果mScrapView中没有的话,则返回null,这个时候的view需要getView()来加载
情景案列:
listView中正好有6个item,此时mScrapView为空,这个时候listView像上滑动一点,则需要展示第7个item,mScrapView中没有,只能通过getView()加载,当第7个item完全显示,则第一个item会被回收,mScrapView中便会有一个item的缓存。
这个时候如果再向上滑动,第8个item需要显示,注意,这里的第8个,实际上对于listView来说是第7个,从mScrapView查询对应下标的item,无
果,只能返回mScrapView中最后一个item作为convert。
如果刚才不是向上滑动,而是向下滑动,第1个item需要显示,从mScrapView中查找,正好有对应下标的item,直接返回做convert。
private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
final int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position or ID.
for (int i = 0; i < size; i++) {
final View view = scrapViews.get(i);
final AbsListView.LayoutParams params =
(AbsListView.LayoutParams) view.getLayoutParams();
//不重写BaseAdapter的hasStableIds方法,这个if就会是false
if (mAdapterHasStableIds) {
final long id = mAdapter.getItemId(position);
if (id == params.itemId) {
return scrapViews.remove(i);
}
} else if (params.scrappedFromPosition == position) {
final View scrap = scrapViews.remove(i);
clearAccessibilityFromScrap(scrap);
return scrap;
}
}
final View scrap = scrapViews.remove(size - 1);
clearAccessibilityFromScrap(scrap);
return scrap;
} else {
return null;
}
}
进入调用流程
ListView的继承关系
Object->View->...->AbsListView->ListView
由于ListView最终还是继承自view的,所以有着View该有的属性,自然也有onMeasure(),onLayout()和onDraw()
一般情况下,如果View的子视图不为空的话,onMeasure()和onLayout()都会执行两遍。
因为最开始父视图是没有子视图的,这个时候会执行一次onMeasure()和onLayout(),然后当你往父视图里添加子视图的时候,父视图的状态会发生变化,重新执行onMeasure()和onLayout()。
listView初始化时必然要经历的过程
1.ListView.onMeasure():
2.AbsListView.onLayout():一般初始化子View的时候,都是调用父视图的onLayout()
2.1 ListView.layoutChildren()
2.2 ListView.fillFromTop()
2.3 ListView.fillDown()
2.4 ListView.makeAndAddView()
2.5 ListView.setupChild()
3.ListView.onDraw():
onMeasure()和onDraw()对于listView来说,并没有什么特殊的,所以下文着重介绍onLayout()的过程。
ListView的onLayout()方法在父类AbsListView中
onLayout(): 该方法中有个if语句,判断当数据发生变化的时候,强制子视图重绘视图。除此之外,就是一个layoutChildren的方法了,这个方法一看就知道是给子类布局的。具体的实现也在子类listView中
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
mInLayout = true;
final int childCount = getChildCount();
if (changed) {
for (int i = 0; i < childCount; i++) {
getChildAt(i).forceLayout();
}
mRecycler.markChildrenDirty();
}
layoutChildren();
mInLayout = false;
.....
}
layoutChildren(): layoutChildren的代码有点多,下面的代码已经把不必要的代码省略,首先会获取子类的数量,这是childCount==0,接下来是两个if判断,判断数据是否发生变化,这里是否,所以跳过两个if,进入swich语句,mLayoutMode默认情况下是LAYOUT_NORMAL,所以进入default,接着两个判断,childCount==0,布局顺序默认从上往下,所以会调用fillFromTop()方法,
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
try {
....
final int childCount = getChildCount();
...
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
....
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();
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
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;
}
...
}
fillFromTop(): 获取mFirstPosition的值,然后调用fillDown()方法
private View fillFromTop(int nextTop) {
mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
if (mFirstPosition < 0) {
mFirstPosition = 0;
}
return fillDown(mFirstPosition, nextTop);
}
fillDown():首先获取listView整个控件的高度end,这里用的是Botton-top-padding.bottom的方式,接下来会进入一个while循环,判断nextTop也就是item的top值是否小于end,小于表示还在屏幕内。同时还要判断pos是否小于mItemCount,mItemCount是Adapter中的item个数。满足两个条件,则往listView中添加item,用的是makeAndAddView()方法。
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;
}
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
makeAndAddView():首先会尝试从RecycleBin获取一个view,可惜第一次是获取不到的,接下来会执行到obtainView()方法,
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
obtainView():代码第四行,尝试从RecycleBin中获取一个view,不过这时返回的结果会是null,接下来一个if语句,假设scrapView!=null,那会调用mAdapter.getView()方法,相信这个方法大家都再熟悉不过了吧!同时将scrapView当成一个参数传给getView,这里的scrapView就是getView中的convert。
那么当scrapView==null时,同样调用mAdapter.getView()方法,只不过这个时候getView中的convert==null,这个时候的convert就需要在你自己重写的getview中初始化,通过LayoutInflater函数。不过如何,最后都会返回一个view。
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
if (child != scrapView) {
mRecycler.addScrapView(scrapView);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
} else {
isScrap[0] = true;
dispatchFinishTemporaryDetach(child);
}
} else {
child = mAdapter.getView(position, null, this);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
}
return child;
}
回到makeAndAddView()中,代码继续执行,调用setupChild()方法,
setupChild():改方法主要是设置子view,所以看相关方法就行了。在第二个if处,判断逻辑有点复杂,没关系,一个个来看。
(recycled&&!p.forceAdd) recycled是一个boolean值,取自mIsScrap[0],初始化的时候为false,后面那个(!p.forceAdd)表示是否添加到布局中,这时应该是没有,所以(!p.forceAdd)==true.
后面一个判断,viewType是否==header or footer,不是,所以最后会执行else,addViewInLayout(),将子视图加到listView中,再结合之前提到的一个while循环,当加载满一个屏幕或者pos大于adapter中的item的数量时跳出。
这就算加载好了一屏的数据了。
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "setupListItem");
final boolean isSelected = selected && shouldShowSelector();
final boolean updateChildSelected = isSelected != child.isSelected();
final int mode = mTouchMode;
final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
mMotionPosition == position;
final boolean updateChildPressed = isPressed != child.isPressed();
final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
// Respect layout params that are already in the view. Otherwise make some up...
// noinspection unchecked
AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
}
p.viewType = mAdapter.getItemViewType(position);
if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
&& p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
} else {
p.forceAdd = false;
if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
p.recycledHeaderFooter = true;
}
addViewInLayout(child, flowDown ? -1 : 0, p, true);
}
if (updateChildSelected) {
child.setSelected(isSelected);
}
if (updateChildPressed) {
child.setPressed(isPressed);
}
if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
if (child instanceof Checkable) {
((Checkable) child).setChecked(mCheckStates.get(position));
} else if (getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
child.setActivated(mCheckStates.get(position));
}
}
if (needToMeasure) {
final int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
mListPadding.left + mListPadding.right, p.width);
final int lpHeight = p.height;
final int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeSafeMeasureSpec(getMeasuredHeight(),
MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
} else {
cleanupLayoutState(child);
}
final int w = child.getMeasuredWidth();
final int h = child.getMeasuredHeight();
final int childTop = flowDown ? y : y - h;
if (needToMeasure) {
final int childRight = childrenLeft + w;
final int childBottom = childTop + h;
child.layout(childrenLeft, childTop, childRight, childBottom);
} else {
child.offsetLeftAndRight(childrenLeft - child.getLeft());
child.offsetTopAndBottom(childTop - child.getTop());
}
if (mCachingStarted && !child.isDrawingCacheEnabled()) {
child.setDrawingCacheEnabled(true);
}
if (recycled && (((AbsListView.LayoutParams)child.getLayoutParams()).scrappedFromPosition)
!= position) {
child.jumpDrawablesToCurrentState();
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
走完上述的流程,第一次的onLayout()就结束了。因为数据加载进来,导致父视图发生变化,所以会执行第二次的onLayout()
第二次onLayout()
第二次onLayout()流程和第一次的流程差不多
layoutChildren():这次childCount!=0,所以下面recycleBin.fillActiveViews()方法会对数据做缓存,接下来会调用detachAllViewsFromParent()方法,将所有的view从父视图中剥离(detach),为了不在第二次layout时,重复加载数据。下面的switch方法中,会进入else语句,并满足第二条if语句,mSelectedPosition在没有选中item的时候,默认应该为-1,mFirstPosition默认为0,所以会进入第二条if语句,执行fillSpecific()方法。
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
if (blockLayoutRequests) {
return;
}
mBlockLayoutRequests = true;
try {
....
final int childCount = getChildCount();
...
boolean dataChanged = mDataChanged;
if (dataChanged) {
handleDataChanged();
}
....
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();
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
if (newSel != null) {
sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
} else {
sel = fillFromMiddle(childrenTop, childrenBottom);
}
break;
case LAYOUT_SYNC:
sel = fillSpecific(mSyncPosition, mSpecificTop);
break;
case LAYOUT_FORCE_BOTTOM:
sel = fillUp(mItemCount - 1, childrenBottom);
adjustViewsUpOrDown();
break;
case LAYOUT_FORCE_TOP:
mFirstPosition = 0;
sel = fillFromTop(childrenTop);
adjustViewsUpOrDown();
break;
case LAYOUT_SPECIFIC:
sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
break;
case LAYOUT_MOVE_SELECTION:
sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
break;
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;
}
...
}
fillSpecific():在fillSpecific方法中,还是调用了makeAndAddView()方法,所以再次进入到makeAndAddView().
/**
* Put a specific item at a specific location on the screen and then build up and down from there.
*/
private View fillSpecific(int position, int top) {
boolean tempIsSelected = position == mSelectedPosition;
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 below;
final int dividerHeight = mDividerHeight;
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;
}
}
再次进入到makeAndAddView():这次直接能从RecycleBin.getActiveView()取得Item了,取得item之后,调用setupChild()方法,注意RecycleBin.getActiveView()方法在返回一个view的同时,会把数据里对应位置的数据删除,所以当listView布局完成之后,对应的mActiveList就会被清空,之后listView滑动的时候,起作用的主要是mScrapView数组。
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
setupChild():同样省略部分代码,看第二个if语句,这个时候的recycled是从makeAndAddView()中传来的值,所以recycled==true,在加上第二次layout()的时候调用了detachAllViewsFromParent()方法,解绑了所有的子view,所以(!p.forceAdd)==true,然后执行下面的attachViewToParent()方法,将子view添加到父布局中。另外,当子view被回收的时候,也会触发detach()方法,解绑,节省资源。
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled) {
...
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
}
p.viewType = mAdapter.getItemViewType(position);
if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter
&& p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
attachViewToParent(child, flowDown ? -1 : 0, p);
} else {
p.forceAdd = false;
if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
p.recycledHeaderFooter = true;
}
addViewInLayout(child, flowDown ? -1 : 0, p, true);
}
...
}
再回到fillSpecific()方法:将第一个item布局好之后,接下来会根据布局模式,从上往下,或者从下往上布局,来调用fillUp和fillDown,通过while循环,将其他的item布局到listView上,直至填满屏幕,或者遍历mAdapter的itemCount。
private View fillSpecific(int position, int top) {
boolean tempIsSelected = position == mSelectedPosition;
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 below;
final int dividerHeight = mDividerHeight;
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;
}
}
至此,第二个onLayout也结束,最后就是onDraw()的事情了。
总结一下:
1.在第一次布局的时候调用我们写的getView()的逻辑代码来加载第一次的数据,
2.因为需要二次布局的原因,将第一次加载的item放到一个临时数组mActiveViews中,用于在第二次makeAndAddView()时复用,
3.通过while循环和makeAndAddView()填满可见屏幕
4.接下来的listView中item的复用主要通过mScrapViews来解决。
下篇将会分析一下recycleView的实现原理。
参考博客:
http://blog.csdn.net/chunqiuwei/article/details/48765007
http://blog.csdn.net/linghu_java/article/details/39496921
http://blog.csdn.net/guolin_blog/article/details/44996879
本人也只是Android开发路上一只稍大一点的菜鸟,如果各位读者中发现文章中有误之处,请帮忙指出,你的批评和鼓励都是我前进的动力。
写在文末:如果读者朋友有什么问题或者意见可以在评论里指出.