最近研究瀑布流代码,顺便学习了下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; }
setupChild(child, position, childrenBottomOrTop, flow, childrenLeft, selected, true);如果数据是新添加或者发生变化,我们需要调用obtainView来获取child。
setupChild 这个方法主要是确保这个子View被测定并且处于合适的位置。