最近研究瀑布流代碼,順便學習了下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 positionchild = 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 positionedchildrenLeft = 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 possiblechild = obtainView(position, mIsScrap);// This needs to be positioned and measuredsetupChild(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被測定並且處於合適的位置。