ListView希望通过复用看不见的View来达到目的。其中最最重要的就是这个
2.RecycleBin--回收站.
回收站并不是垃圾站。
最重要的是两个成员变量 mScrapViews 和mActiveViews, mScrapViews 表示回收站的View. mActiveViews表示激活的View,激活的View是可以直接拿来显示的,不想要重新调用getView填充数据。激活的View(mActiveViews)和废弃的View(mScrapViews) 不同,激活的View不想要调用getView填充数据,而废弃的View需要。
/** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; private View[] mActiveViews = new View[0];
看不见的View都会放进这里面。主要是通过以下方法 addScrapView 加进去的
/** * Put a view into the ScapViews list. These views are unordered. * * @param scrap The view to add */ void addScrapView(View scrap, int position) { AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } // Don't put header or footer views or views that should be ignored // into the scrap heap int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(scrap, false); } return; } lp.scrappedFromPosition = position; if (mViewTypeCount == 1) { scrap.dispatchStartTemporaryDetach(); mCurrentScrap.add(scrap); } else { scrap.dispatchStartTemporaryDetach(); mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } }
3.回收站算法
makeAndAddView 这个方法显示了回收站的使用算法。如果没有dadachange,那么就直接从getActiveView 方法取得那个View,如果成功,就调整其位置。如果datachange,就需要重新绑定数据到这个View上。这里的 重新绑定数据 方法obtainView 同时考虑是否使用回收站的View.
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) { if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP, position, getChildCount()); } // 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; }
4.通过ObtainView方法绑定数据
其实也很简单。主要任务是复用mScrapViews。
View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP, position, -1); } child = mAdapter.getView(position, scrapView, this); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW, position, getChildCount()); } if (child != scrapView) { mRecycler.addScrapView(scrapView, position); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, position, -1); } } else { isScrap[0] = true; child.dispatchFinishTemporaryDetach(); } } else { child = mAdapter.getView(position, null, this); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW, position, getChildCount()); } } return child; }
5.布局listView
fillDown 方法用得最多,其目的就是从pos这个index和nextTop这个View top坐标,向下填充Views直到底部。
/**
* Fills the list from pos down to the end of the list view.
*
* @param pos The first position to put in the list
*
* @param nextTop The location where the top of the item associated with pos
* should be drawn
*
* @return The view that is currently selected, if it happens to be in the
* range that we draw.
*/
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++;
}
return selectedView;
}
6.触发fillDown的切入点
layoutChildren
trackMotionScroll
7.不得不说的LayoutParams
/** * AbsListView extends LayoutParams to provide a place to hold the view type. */ public static class LayoutParams extends ViewGroup.LayoutParams { /** * View type for this view, as returned by * {@link android.widget.Adapter#getItemViewType(int) } */ @ViewDebug.ExportedProperty(category = "list", mapping = { @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") }) int viewType; /** * When this boolean is set, the view has been added to the AbsListView * at least once. It is used to know whether headers/footers have already * been added to the list view and whether they should be treated as * recycled views or not. */ @ViewDebug.ExportedProperty(category = "list") boolean recycledHeaderFooter; /** * When an AbsListView is measured with an AT_MOST measure spec, it needs * to obtain children views to measure itself. When doing so, the children * are not attached to the window, but put in the recycler which assumes * they've been attached before. Setting this flag will force the reused * view to be attached to the window rather than just attached to the * parent. */ @ViewDebug.ExportedProperty(category = "list") boolean forceAdd; /** * The position the view was removed from when pulled out of the * scrap heap. * @hide */ int scrappedFromPosition; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int w, int h) { super(w, h); } public LayoutParams(int w, int h, int viewType) { super(w, h); this.viewType = viewType; } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } }
8.提高布局性能的函数 offsetLeftAndRight和 offsetTopAndBottom
在setupChild函数 中可以看到如下两行
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()); }
9. 提高性能的函数addViewInLayout
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); }