最近研究瀑布流代码,顺便学习了下ListView的源码。这里记录下。
ListView源码里已经对ListView的性能进行了极大化的优化,这里主要用到了回收站(RecycleBin)这么个东西。
RecycleBin促进布局视图的重用,回收站有两个层级的存储:ActiveViews和ScrapViews。ActiveViews是指那些布局开始显示在屏幕上的Views,它们显示当前信息。在布局的底部,所有ActiveViews被降级为ScrapViews。ScrapViews是指那些有可能被Adapter使用以避免重复分配的view。就是说回收站维持着一个序列化的集合,这个集合里的view可能要比一个屏幕能显示的view的个数稍微多些。当listview滑动时,一些不可见的ActiveViews将会被降级到ScrapViews。
下面将一条重要的调用路线记录下。onLayout------->layoutChildren---------->fillFromTop---------->fillDown------->
makeAndAddView------->obtainView.反过来记录下每个函数的具体细节。
obtainView.这个方法是用来获取一个view然后让它带着数据在特定的位置显示。当我们发现这个view在回收站不可用时我们调用这个方法,这时候我们唯一的选择就是转换一个旧的view或者创建一个新的view。贴上代码如下。
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);
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();
dispatchFinishTemporaryDetach(child);
}
} 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;
}
第一次走肯定会走到else条件里去。所以child = mAdapter.getView(position, null, this);所以在我们的Adapter里convertView传入的是null,这时候需要我们的adapter inflate 我们的listview item layout。
当scrapView不为null时,就会复用view而不会再创建,从而优化了listview。在crapView = mRecycler.getScrapView(position);我们追踪getScrapView的代码发现,ScrapView其实是无序的,如果listview
的type只有一种的话,这个position是没有任何意义的。看下代码。
View getScrapView(int position) {
ArrayList<View> scrapViews;
if (mViewTypeCount == 1) {
scrapViews = mCurrentScrap;
int size = scrapViews.size();
if (size > 0) {
return scrapViews.remove(size - 1);
} else {
return null;
}
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
scrapViews = mScrapViews[whichScrap];
int size = scrapViews.size();
if (size > 0) {
return scrapViews.remove(size - 1);
}
}
}
return null;
}
scrapViews 什么时候被初始化或者赋值的哪?在layoutChildren里执行的:recycleBin.scrapActiveViews();
这个方法将activeview降级为scrapview。
makeAndAddView 取得View并把它添加到我们的子视图列表里。这个View可以是新的,或者是从未使用的View转换以及从回收站拿过来的。
private View makeAndAddView(int position, int childrenBottomOrTop, boolean flow,
boolean selected) {
View child;
int childrenLeft;
if (!mDataChanged) {
// Try to use an exsiting 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
childrenLeft = getItemLeft(position);
setupChild(child, position, childrenBottomOrTop, flow, childrenLeft, selected, true);
return child;
}
}
//Notify new item is added to view.
onItemAddedToList( position, flow );
//获取开始绘制时距离左边屏幕边框的距离
childrenLeft = getItemLeft( position );
// 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, childrenBottomOrTop, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
如果数据没有发生改变,我们直接从回收站的ActiveView里拿出那个位置的View,获取这个View的左边距,然后
setupChild(child, position, childrenBottomOrTop, flow, childrenLeft, selected, true);
如果数据是新添加或者发生变化,我们需要调用obtainView来获取child。
setupChild 这个方法主要是确保这个子View被测定并且处于合适的位置。