在上一篇文章中,我们提到过ListView是通过makeAndAddView来填充子view的。而这就涉及到了ListView最重要的View复用机制。这个复用机制是通过RecycleBin来实现的。
一、RecycleBin
RecycleBin是AbsListView的一个内部类。作用是来复用AbsListView的子view, 它就像一个cell view的池子,来给AbsListView提供可复用的cell view.
实际上,RecycleBin包含了四种cell view的缓存池。
1.private View[] mActiveViews,存储的cell view可以直接使用,不需要再绑定数据。
2.private ArrayList
3.private SparseArray
4.private ArrayList
RecycleBin主要使用的是mActiveViews和mScrapViews两个池子。在开始布局的时候,如果ListView的数据没有发生改变,直接把所有的cell view添加到mActiveViews中,如果数据发生了改变,就把所有的cell view添加到mScrapViews中。然后ListView开始布局,如果数据没有改变,就从mActiveViews中重用视图,如果改变了,就从mScrapViews中重用。布局结束后,检查mActiveViews中是否还有cell view,如果有就把他们全部添加到mScrapViews。
mActiveViews和mScrapViews都非常重要,他们的差别在于,mScrapViews需要重新调用adapter的getView来对cell view和item数据进行绑定,而mActiveViews的cell view可以直接使用。
1.测量过程中调用方法
ListView在进行测量时,会获取一个cell view的宽度和高度,这个获取的方法需要RecycleBin的配合,从池子中得一个view,在使用之后还需要把它放回到相应的位置。
从RecycleBin中获取cell view时,如果得到的是null,会通过getView来得到一个cell view. 使用完成后会把它放到RecycleBin中。
获取和放回都是由Recycle的getScrapView和addScrapView来完成的。
getScrapView
在测量时获取cell view, 会通过getScrapView来得到,如果mScrapViews中没有对应的cell view,返回null.
mScrapViews是List的数组,可以区分不同的type。每种类型是数组的一个元素,是一个cell view的List.
getScrapView会根据对应的位置来得到对应的类型,然后根据类型从mScrapViews中取得一个cell view返回。
/**
* @return A view from the ScrapViews collection. These are unordered.
*/
View getScrapView(int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}
static View retrieveFromScrap(ArrayList scrapViews, int position) {
int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position.
for (int i=0; i
View view = scrapViews.get(i);
if (((AbsListView.LayoutParams)view.getLayoutParams())
.scrappedFromPosition == position) {
scrapViews.remove(i);
return view;
}
}
return scrapViews.remove(size - 1);
} else {
return null;
}
}
addScrapView
addScrapView是把cell view添加到mScrapViews中,这些cell view可以是显示状态、临时状态或者废弃状态三种类型。如果cell view是临时状态,会直接放入到对应的mTransientStateViews或mSkippedScrap中。
void addScrapView(View scrap, int position) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
lp.scrappedFromPosition = position;
// Don't put header or footer views or views that should be ignored
// into the scrap heap
int viewType = lp.viewType;
final boolean scrapHasTransientState = scrap.hasTransientState();
if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList();
}
mSkippedScrap.add(scrap);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray();
}
scrap.dispatchStartTemporaryDetach();
mTransientStateViews.put(position, scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
scrap.setAccessibilityDelegate(null);
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
2.布局前调用的方法
主要是layoutChildren方法。
布局时,先判断ListView的数据是否改变,如果改变,调用addScrapView把所有的cell view全部加入到mScrapViews,这一步也是cell view的显示状态转化为废弃状态。
如果数据没有发生变化。调用fillActiveViews把所有的cell view全部加入到mActiveViews中,这一步是cell view显示状态转化为活跃状态。
从以上过程也可以得出mActiveViews和mScrapViews的区别就是在于是否需要再次绑定数据。
在完成添加操作之后,会调用removeSkippedScrap()来把mSkippedScrap中的所有cell view清空。
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;
}
}
}
- 布局中调用makeAndAddView方法
makeAndAddView首先判断ListView的数据是否发生了改变,如果没有改变,直接mActiveViews转化为显示视图。调用getActiveView方法取得对应的cell view 即可。
如果数据发生了变化,会调用adapter的getView方法,其中需要去对应的mScrapViews中找到缓存的cell view传入作为getView的参数。
- 布局后调用方法
布局之前把所有显示状态的cell view加入到mActiveViews或者mScrapViews中。布局中,从重用视图池中获取相应的cell view转化成显示的cell view. 布局之后,主要来处理mActiveViews中没有使用的cell view,因为这次布局之后,这些余下的cell view没有被重用,所以把它们转化为废弃状态,使用的方法是scrapActiveViews().
void scrapActiveViews() {
final View[] activeViews = mActiveViews;
final boolean hasListener = mRecyclerListener != null;
final boolean multipleScraps = mViewTypeCount > 1;
ArrayList scrapViews = mCurrentScrap;
final int count = activeViews.length;
for (int i = count - 1; i >= 0; i--) {
final View victim = activeViews[i];
if (victim != null) {
final AbsListView.LayoutParams lp
= (AbsListView.LayoutParams) victim.getLayoutParams();
int whichScrap = lp.viewType;
activeViews[i] = null;
final boolean scrapHasTransientState = victim.hasTransientState();
if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
// Do not move views that should be ignored
if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
scrapHasTransientState) {
removeDetachedView(victim, false);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray();
}
mTransientStateViews.put(mFirstActivePosition + i, victim);
}
continue;
}
if (multipleScraps) {
scrapViews = mScrapViews[whichScrap];
}
victim.dispatchStartTemporaryDetach();
lp.scrappedFromPosition = mFirstActivePosition + i;
scrapViews.add(victim);
victim.setAccessibilityDelegate(null);
if (hasListener) {
mRecyclerListener.onMovedToScrapHeap(victim);
}
}
}
pruneScrapViews();
}
这个方法遍历mActiveViews,找到没有被重用的cell view。对于没有被重用的cell view,如果有临时状态,把cell view加入mTransientStateViews直接并从父视图中移除。如果不需要加回收就直接从父视图中移除。如果应该回收就添加到mScrapViews。
二、makeAndAddView流程
makeAndAddView先获取一个cell view,然后把它添加到ListView中。添加主要是通过setupChild方法来设置,而获取cell view需要根据数据有没有改变来判断,如果没有变化,直接从mActiveViews中取得,如果有变化需要从RecycleBin的mScrapViews或mTransientStateViews中取得。
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流程
如果在makeAndAddView中数据发生了变化,或者没有改变但是从mActiveViews中没有取到对应的cell view时,就需要使用obtainView。
View obtainView(int position, boolean[] isScrap) {
isScrap[0] = false;
View scrapView;
scrapView = mRecycler.getTransientStateView(position);
if (scrapView != null) {
return scrapView;
}
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
} else {
isScrap[0] = true;
child.dispatchFinishTemporaryDetach();
}
} else {
child = mAdapter.getView(position, null, this);
if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
}
if (mAdapterHasStableIds) {
final ViewGroup.LayoutParams vlp = child.getLayoutParams();
LayoutParams lp;
if (vlp == null) {
lp = (LayoutParams) generateDefaultLayoutParams();
} else if (!checkLayoutParams(vlp)) {
lp = (LayoutParams) generateLayoutParams(vlp);
} else {
lp = (LayoutParams) vlp;
}
lp.itemId = mAdapter.getItemId(position);
child.setLayoutParams(lp);
}
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new ListItemAccessibilityDelegate();
}
child.setAccessibilityDelegate(mAccessibilityDelegate);
}
return child;
}
obtainView时,首先需要检查mTransientStateViews,如果有对应的cell view 直接返回。没有就去检查mScrapViews。然后调用adapter的getView方法,如果得到对应的view就使用getView来绑定数据,如果没有就生成一个新的cell view.
四、setupChild方法
setupChild主要是把cell view添加到ListView中,并放到正确的位置。
private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled) {
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) {
int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
mListPadding.left + mListPadding.right, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, 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();
}
}
简单来说,setupChild有三个步骤,添加子视图,测量子视图,布局了视图。测量和布局可能会会跳过。
首先,确定子视图是否被选择,是否更新选择状态,是否按下,是否更新按下状态。根据这四个状态来确定是否需要测量,如果这个cell view不是重用来的或者需要更新选择状态,或者更新按下状态,那么就需要重新测量。
如果cell view是重用的,调用attachViewToParent添加到ListView中。否则就调用addViewInLayout来添加。
总结一下就是ListView通过RecycleBin来实现cell view的缓存。缓存有四种状态,加上显示状态,这些状态大多都在layoutChildren中转化。
makeAndAddView主要有两个功能,一是生成一个cell view,二是把cell view添加到ListView中。