ListView實現原理

轉載:http://blog.csdn.net/guolin_blog/article/details/44996879java

Android全部經常使用的原生控件當中,用法最複雜的應該就是ListView了,它專門用於處理那種內容元素不少,手機屏幕沒法展現出全部內容的狀況。ListView可使用列表的形式來展現內容,超出屏幕部分的內容只須要經過手指滑動就能夠移動到屏幕內了。android

另外ListView還有一個很是神奇的功能,我相信你們應該都體驗過,即便在ListView中加載很是很是多的數據,好比達到成百上千條甚至更多,ListView都不會發生OOM或者崩潰,並且隨着咱們手指滑動來瀏覽更多數據時,程序所佔用的內存居然都不會跟着增加。那麼ListView是怎麼實現這麼神奇的功能的呢?當初我就抱着學習的心態花了很長時間把ListView的源碼通讀了一遍,基本瞭解了它的工做原理,在感嘆Google大神可以寫出如此精妙代碼的同時我也有所敬畏,由於ListView的代碼量比較大,複雜度也很高,很難用文字表達清楚,因而我就放棄了把它寫成一篇博客的想法。那麼如今回想起來這件事我已經腸子都悔青了,由於沒過幾個月時間我就把當初梳理清晰的源碼又忘的一乾二淨。因而如今我又從新定下心來再次把ListView的源碼重讀了一遍,那麼此次我必定要把它寫成一篇博客,分享給你們的同時也當成我本身的筆記吧。算法

首先咱們先來看一下ListView的繼承結構,以下圖所示:數據庫

能夠看到,ListView的繼承結構仍是至關複雜的,它是直接繼承自的AbsListView,而AbsListView有兩個子實現類,一個是ListView,另外一個就是GridView,所以咱們從這一點就能夠猜出來,ListView和GridView在工做原理和實現上都是有不少共同點的。而後AbsListView又繼承自AdapterView,AdapterView繼承自ViewGroup,後面就是咱們所熟知的了。先把ListView的繼承結構瞭解一下,待會兒有助於咱們更加清晰地分析代碼。數組

 

Adapter的做用

 

Adapter相信你們都不會陌生,咱們平時使用ListView的時候必定都會用到它。那麼話說回來你們有沒有仔細想過,爲何須要Adapter這個東西呢?總感受正由於有了Adapter,ListView的使用變得要比其它控件複雜得多。那麼這裏咱們就先來學習一下Adapter到底起到了什麼樣的一個做用。緩存

其實說到底,控件就是爲了交互和展現數據用的,只不過ListView更加特殊,它是爲了展現不少不少數據用的,可是ListView只承擔交互和展現工做而已,至於這些數據來自哪裏,ListView是不關心的。所以,咱們能設想到的最基本的ListView工做模式就是要有一個ListView控件和一個數據源。微信

不過若是真的讓ListView和數據源直接打交道的話,那ListView所要作的適配工做就很是繁雜了。由於數據源這個概念太模糊了,咱們只知道它包含了不少數據而已,至於這個數據源究竟是什麼樣類型,並無嚴格的定義,有多是數組,也有多是集合,甚至有多是數據庫表中查詢出來的遊標。因此說若是ListView真的去爲每一種數據源都進行適配操做的話,一是擴展性會比較差,內置了幾種適配就只有幾種適配,不能動態進行添加。二是超出了它自己應該負責的工做範圍,再也不是僅僅承擔交互和展現工做就能夠了,這樣ListView就會變得比較臃腫。app

那麼顯然Android開發團隊是不會容許這種事情發生的,因而就有了Adapter這樣一個機制的出現。顧名思義,Adapter是適配器的意思,它在ListView和數據源之間起到了一個橋樑的做用,ListView並不會直接和數據源打交道,而是會藉助Adapter這個橋樑來去訪問真正的數據源,與以前不一樣的是,Adapter的接口都是統一的,所以ListView不用再去擔憂任何適配方面的問題。而Adapter又是一個接口(interface),它能夠去實現各類各樣的子類,每一個子類都能經過本身的邏輯來去完成特定的功能,以及與特定數據源的適配操做,好比說ArrayAdapter能夠用於數組和List類型的數據源適配,SimpleCursorAdapter能夠用於遊標類型的數據源適配,這樣就很是巧妙地把數據源適配困難的問題解決掉了,而且還擁有至關不錯的擴展性。簡單的原理示意圖以下所示:異步

固然Adapter的做用不只僅只有數據源適配這一點,還有一個很是很是重要的方法也須要咱們在Adapter當中去重寫,就是getView()方法,這個在下面的文章中還會詳細講到。ide

RecycleBin機制

那麼在開始分析ListView的源碼以前,還有一個東西是咱們提早須要瞭解的,就是RecycleBin機制,這個機制也是ListView可以實現成百上千條數據都不會OOM最重要的一個緣由。其實RecycleBin的代碼並很少,只有300行左右,它是寫在AbsListView中的一個內部類,因此全部繼承自AbsListView的子類,也就是ListView和GridView,均可以使用這個機制。那咱們來看一下RecycleBin中的主要代碼,以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * The RecycleBin facilitates reuse of views across layouts. The RecycleBin 
  3.  * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are 
  4.  * those views which were onscreen at the start of a layout. By 
  5.  * construction, they are displaying current information. At the end of 
  6.  * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews 
  7.  * are old views that could potentially be used by the adapter to avoid 
  8.  * allocating views unnecessarily. 
  9.  *  
  10.  * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 
  11.  * @see android.widget.AbsListView.RecyclerListener 
  12.  */  
  13. class RecycleBin {  
  14.     private RecyclerListener mRecyclerListener;  
  15.   
  16.     /** 
  17.      * The position of the first view stored in mActiveViews. 
  18.      */  
  19.     private int mFirstActivePosition;  
  20.   
  21.     /** 
  22.      * Views that were on screen at the start of layout. This array is 
  23.      * populated at the start of layout, and at the end of layout all view 
  24.      * in mActiveViews are moved to mScrapViews. Views in mActiveViews 
  25.      * represent a contiguous range of Views, with position of the first 
  26.      * view store in mFirstActivePosition. 
  27.      */  
  28.     private View[] mActiveViews = new View[0];  
  29.   
  30.     /** 
  31.      * Unsorted views that can be used by the adapter as a convert view. 
  32.      */  
  33.     private ArrayList<View>[] mScrapViews;  
  34.   
  35.     private int mViewTypeCount;  
  36.   
  37.     private ArrayList<View> mCurrentScrap;  
  38.   
  39.     /** 
  40.      * Fill ActiveViews with all of the children of the AbsListView. 
  41.      *  
  42.      * @param childCount 
  43.      *            The minimum number of views mActiveViews should hold 
  44.      * @param firstActivePosition 
  45.      *            The position of the first view that will be stored in 
  46.      *            mActiveViews 
  47.      */  
  48.     void fillActiveViews(int childCount, int firstActivePosition) {  
  49.         if (mActiveViews.length < childCount) {  
  50.             mActiveViews = new View[childCount];  
  51.         }  
  52.         mFirstActivePosition = firstActivePosition;  
  53.         final View[] activeViews = mActiveViews;  
  54.         for (int i = 0; i < childCount; i++) {  
  55.             View child = getChildAt(i);  
  56.             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();  
  57.             // Don't put header or footer views into the scrap heap  
  58.             if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  59.                 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in  
  60.                 // active views.  
  61.                 // However, we will NOT place them into scrap views.  
  62.                 activeViews[i] = child;  
  63.             }  
  64.         }  
  65.     }  
  66.   
  67.     /** 
  68.      * Get the view corresponding to the specified position. The view will 
  69.      * be removed from mActiveViews if it is found. 
  70.      *  
  71.      * @param position 
  72.      *            The position to look up in mActiveViews 
  73.      * @return The view if it is found, null otherwise 
  74.      */  
  75.     View getActiveView(int position) {  
  76.         int index = position - mFirstActivePosition;  
  77.         final View[] activeViews = mActiveViews;  
  78.         if (index >= 0 && index < activeViews.length) {  
  79.             final View match = activeViews[index];  
  80.             activeViews[index] = null;  
  81.             return match;  
  82.         }  
  83.         return null;  
  84.     }  
  85.   
  86.     /** 
  87.      * Put a view into the ScapViews list. These views are unordered. 
  88.      *  
  89.      * @param scrap 
  90.      *            The view to add 
  91.      */  
  92.     void addScrapView(View scrap) {  
  93.         AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();  
  94.         if (lp == null) {  
  95.             return;  
  96.         }  
  97.         // Don't put header or footer views or views that should be ignored  
  98.         // into the scrap heap  
  99.         int viewType = lp.viewType;  
  100.         if (!shouldRecycleViewType(viewType)) {  
  101.             if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  102.                 removeDetachedView(scrap, false);  
  103.             }  
  104.             return;  
  105.         }  
  106.         if (mViewTypeCount == 1) {  
  107.             dispatchFinishTemporaryDetach(scrap);  
  108.             mCurrentScrap.add(scrap);  
  109.         } else {  
  110.             dispatchFinishTemporaryDetach(scrap);  
  111.             mScrapViews[viewType].add(scrap);  
  112.         }  
  113.   
  114.         if (mRecyclerListener != null) {  
  115.             mRecyclerListener.onMovedToScrapHeap(scrap);  
  116.         }  
  117.     }  
  118.   
  119.     /** 
  120.      * @return A view from the ScrapViews collection. These are unordered. 
  121.      */  
  122.     View getScrapView(int position) {  
  123.         ArrayList<View> scrapViews;  
  124.         if (mViewTypeCount == 1) {  
  125.             scrapViews = mCurrentScrap;  
  126.             int size = scrapViews.size();  
  127.             if (size > 0) {  
  128.                 return scrapViews.remove(size - 1);  
  129.             } else {  
  130.                 return null;  
  131.             }  
  132.         } else {  
  133.             int whichScrap = mAdapter.getItemViewType(position);  
  134.             if (whichScrap >= 0 && whichScrap < mScrapViews.length) {  
  135.                 scrapViews = mScrapViews[whichScrap];  
  136.                 int size = scrapViews.size();  
  137.                 if (size > 0) {  
  138.                     return scrapViews.remove(size - 1);  
  139.                 }  
  140.             }  
  141.         }  
  142.         return null;  
  143.     }  
  144.   
  145.     public void setViewTypeCount(int viewTypeCount) {  
  146.         if (viewTypeCount < 1) {  
  147.             throw new IllegalArgumentException("Can't have a viewTypeCount < 1");  
  148.         }  
  149.         // noinspection unchecked  
  150.         ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];  
  151.         for (int i = 0; i < viewTypeCount; i++) {  
  152.             scrapViews[i] = new ArrayList<View>();  
  153.         }  
  154.         mViewTypeCount = viewTypeCount;  
  155.         mCurrentScrap = scrapViews[0];  
  156.         mScrapViews = scrapViews;  
  157.     }  
  158.   
  159. }  

 

這裏的RecycleBin代碼並不全,我只是把最主要的幾個方法提了出來。那麼咱們先來對這幾個方法進行簡單解讀,這對後面分析ListView的工做原理將會有很大的幫助。

  • fillActiveViews() 這個方法接收兩個參數,第一個參數表示要存儲的view的數量,第二個參數表示ListView中第一個可見元素的position值。RecycleBin當中使用mActiveViews這個數組來存儲View,調用這個方法後就會根據傳入的參數來將ListView中的指定元素存儲到mActiveViews數組當中。
  • getActiveView() 這個方法和fillActiveViews()是對應的,用於從mActiveViews數組當中獲取數據。該方法接收一個position參數,表示元素在ListView當中的位置,方法內部會自動將position值轉換成mActiveViews數組對應的下標值。須要注意的是,mActiveViews當中所存儲的View,一旦被獲取了以後就會從mActiveViews當中移除,下次獲取一樣位置的View將會返回null,也就是說mActiveViews不能被重複利用。
  • addScrapView() 用於將一個廢棄的View進行緩存,該方法接收一個View參數,當有某個View肯定要廢棄掉的時候(好比滾動出了屏幕),就應該調用這個方法來對View進行緩存,RecycleBin當中使用mScrapViews和mCurrentScrap這兩個List來存儲廢棄View。
  • getScrapView 用於從廢棄緩存中取出一個View,這些廢棄緩存中的View是沒有順序可言的,所以getScrapView()方法中的算法也很是簡單,就是直接從mCurrentScrap當中獲取尾部的一個scrap view進行返回。
  • setViewTypeCount() 咱們都知道Adapter當中能夠重寫一個getViewTypeCount()來表示ListView中有幾種類型的數據項,而setViewTypeCount()方法的做用就是爲每種類型的數據項都單獨啓用一個RecycleBin緩存機制。實際上,getViewTypeCount()方法一般狀況下使用的並非不少,因此咱們只要知道RecycleBin當中有這樣一個功能就好了。

瞭解了RecycleBin中的主要方法以及它們的用處以後,下面就能夠開始來分析ListView的工做原理了,這裏我將仍是按照之前分析源碼的方式來進行,即跟着主線執行流程來逐步閱讀並點到即止,否則的話要是把ListView全部的代碼都貼出來,那麼本篇文章將會很長很長了。

第一次Layout

無論怎麼說,ListView即便再特殊最終仍是繼承自View的,所以它的執行流程還將會按照View的規則來執行,對於這方面不太熟悉的朋友能夠參考我以前寫的 Android視圖繪製流程徹底解析,帶你一步步深刻了解View(二) 。

View的執行流程無非就分爲三步,onMeasure()用於測量View的大小,onLayout()用於肯定View的佈局,onDraw()用於將View繪製到界面上。而在ListView當中,onMeasure()並無什麼特殊的地方,由於它終歸是一個View,佔用的空間最多而且一般也就是整個屏幕。onDraw()在ListView當中也沒有什麼意義,由於ListView自己並不負責繪製,而是由ListView當中的子元素來進行繪製的。那麼ListView大部分的神奇功能其實都是在onLayout()方法中進行的了,所以咱們本篇文章也是主要分析的這個方法裏的內容。

若是你到ListView源碼中去找一找,你會發現ListView中是沒有onLayout()這個方法的,這是由於這個方法是在ListView的父類AbsListView中實現的,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Subclasses should NOT override this method but {@link #layoutChildren()} 
  3.  * instead. 
  4.  */  
  5. @Override  
  6. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  7.     super.onLayout(changed, l, t, r, b);  
  8.     mInLayout = true;  
  9.     if (changed) {  
  10.         int childCount = getChildCount();  
  11.         for (int i = 0; i < childCount; i++) {  
  12.             getChildAt(i).forceLayout();  
  13.         }  
  14.         mRecycler.markChildrenDirty();  
  15.     }  
  16.     layoutChildren();  
  17.     mInLayout = false;  
  18. }  

能夠看到,onLayout()方法中並無作什麼複雜的邏輯操做,主要就是一個判斷,若是ListView的大小或者位置發生了變化,那麼changed變量就會變成true,此時會要求全部的子佈局都強制進行重繪。除此以外倒沒有什麼難理解的地方了,不過咱們注意到,在第16行調用了layoutChildren()這個方法,從方法名上咱們就能夠猜出這個方法是用來進行子元素佈局的,不過進入到這個方法當中你會發現這是個空方法,沒有一行代碼。這固然是能夠理解的了,由於子元素的佈局應該是由具體的實現類來負責完成的,而不是由父類完成。那麼進入ListView的layoutChildren()方法,代碼以下所示:

[java]  view plain  copy
 
  1. @Override  
  2. protected void layoutChildren() {  
  3.     final boolean blockLayoutRequests = mBlockLayoutRequests;  
  4.     if (!blockLayoutRequests) {  
  5.         mBlockLayoutRequests = true;  
  6.     } else {  
  7.         return;  
  8.     }  
  9.     try {  
  10.         super.layoutChildren();  
  11.         invalidate();  
  12.         if (mAdapter == null) {  
  13.             resetList();  
  14.             invokeOnItemScrollListener();  
  15.             return;  
  16.         }  
  17.         int childrenTop = mListPadding.top;  
  18.         int childrenBottom = getBottom() - getTop() - mListPadding.bottom;  
  19.         int childCount = getChildCount();  
  20.         int index = 0;  
  21.         int delta = 0;  
  22.         View sel;  
  23.         View oldSel = null;  
  24.         View oldFirst = null;  
  25.         View newSel = null;  
  26.         View focusLayoutRestoreView = null;  
  27.         // Remember stuff we will need down below  
  28.         switch (mLayoutMode) {  
  29.         case LAYOUT_SET_SELECTION:  
  30.             index = mNextSelectedPosition - mFirstPosition;  
  31.             if (index >= 0 && index < childCount) {  
  32.                 newSel = getChildAt(index);  
  33.             }  
  34.             break;  
  35.         case LAYOUT_FORCE_TOP:  
  36.         case LAYOUT_FORCE_BOTTOM:  
  37.         case LAYOUT_SPECIFIC:  
  38.         case LAYOUT_SYNC:  
  39.             break;  
  40.         case LAYOUT_MOVE_SELECTION:  
  41.         default:  
  42.             // Remember the previously selected view  
  43.             index = mSelectedPosition - mFirstPosition;  
  44.             if (index >= 0 && index < childCount) {  
  45.                 oldSel = getChildAt(index);  
  46.             }  
  47.             // Remember the previous first child  
  48.             oldFirst = getChildAt(0);  
  49.             if (mNextSelectedPosition >= 0) {  
  50.                 delta = mNextSelectedPosition - mSelectedPosition;  
  51.             }  
  52.             // Caution: newSel might be null  
  53.             newSel = getChildAt(index + delta);  
  54.         }  
  55.         boolean dataChanged = mDataChanged;  
  56.         if (dataChanged) {  
  57.             handleDataChanged();  
  58.         }  
  59.         // Handle the empty set by removing all views that are visible  
  60.         // and calling it a day  
  61.         if (mItemCount == 0) {  
  62.             resetList();  
  63.             invokeOnItemScrollListener();  
  64.             return;  
  65.         } else if (mItemCount != mAdapter.getCount()) {  
  66.             throw new IllegalStateException("The content of the adapter has changed but "  
  67.                     + "ListView did not receive a notification. Make sure the content of "  
  68.                     + "your adapter is not modified from a background thread, but only "  
  69.                     + "from the UI thread. [in ListView(" + getId() + ", " + getClass()   
  70.                     + ") with Adapter(" + mAdapter.getClass() + ")]");  
  71.         }  
  72.         setSelectedPositionInt(mNextSelectedPosition);  
  73.         // Pull all children into the RecycleBin.  
  74.         // These views will be reused if possible  
  75.         final int firstPosition = mFirstPosition;  
  76.         final RecycleBin recycleBin = mRecycler;  
  77.         // reset the focus restoration  
  78.         View focusLayoutRestoreDirectChild = null;  
  79.         // Don't put header or footer views into the Recycler. Those are  
  80.         // already cached in mHeaderViews;  
  81.         if (dataChanged) {  
  82.             for (int i = 0; i < childCount; i++) {  
  83.                 recycleBin.addScrapView(getChildAt(i));  
  84.                 if (ViewDebug.TRACE_RECYCLER) {  
  85.                     ViewDebug.trace(getChildAt(i),  
  86.                             ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);  
  87.                 }  
  88.             }  
  89.         } else {  
  90.             recycleBin.fillActiveViews(childCount, firstPosition);  
  91.         }  
  92.         // take focus back to us temporarily to avoid the eventual  
  93.         // call to clear focus when removing the focused child below  
  94.         // from messing things up when ViewRoot assigns focus back  
  95.         // to someone else  
  96.         final View focusedChild = getFocusedChild();  
  97.         if (focusedChild != null) {  
  98.             // TODO: in some cases focusedChild.getParent() == null  
  99.             // we can remember the focused view to restore after relayout if the  
  100.             // data hasn't changed, or if the focused position is a header or footer  
  101.             if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {  
  102.                 focusLayoutRestoreDirectChild = focusedChild;  
  103.                 // remember the specific view that had focus  
  104.                 focusLayoutRestoreView = findFocus();  
  105.                 if (focusLayoutRestoreView != null) {  
  106.                     // tell it we are going to mess with it  
  107.                     focusLayoutRestoreView.onStartTemporaryDetach();  
  108.                 }  
  109.             }  
  110.             requestFocus();  
  111.         }  
  112.         // Clear out old views  
  113.         detachAllViewsFromParent();  
  114.         switch (mLayoutMode) {  
  115.         case LAYOUT_SET_SELECTION:  
  116.             if (newSel != null) {  
  117.                 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);  
  118.             } else {  
  119.                 sel = fillFromMiddle(childrenTop, childrenBottom);  
  120.             }  
  121.             break;  
  122.         case LAYOUT_SYNC:  
  123.             sel = fillSpecific(mSyncPosition, mSpecificTop);  
  124.             break;  
  125.         case LAYOUT_FORCE_BOTTOM:  
  126.             sel = fillUp(mItemCount - 1, childrenBottom);  
  127.             adjustViewsUpOrDown();  
  128.             break;  
  129.         case LAYOUT_FORCE_TOP:  
  130.             mFirstPosition = 0;  
  131.             sel = fillFromTop(childrenTop);  
  132.             adjustViewsUpOrDown();  
  133.             break;  
  134.         case LAYOUT_SPECIFIC:  
  135.             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);  
  136.             break;  
  137.         case LAYOUT_MOVE_SELECTION:  
  138.             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);  
  139.             break;  
  140.         default:  
  141.             if (childCount == 0) {  
  142.                 if (!mStackFromBottom) {  
  143.                     final int position = lookForSelectablePosition(0, true);  
  144.                     setSelectedPositionInt(position);  
  145.                     sel = fillFromTop(childrenTop);  
  146.                 } else {  
  147.                     final int position = lookForSelectablePosition(mItemCount - 1, false);  
  148.                     setSelectedPositionInt(position);  
  149.                     sel = fillUp(mItemCount - 1, childrenBottom);  
  150.                 }  
  151.             } else {  
  152.                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {  
  153.                     sel = fillSpecific(mSelectedPosition,  
  154.                             oldSel == null ? childrenTop : oldSel.getTop());  
  155.                 } else if (mFirstPosition < mItemCount) {  
  156.                     sel = fillSpecific(mFirstPosition,  
  157.                             oldFirst == null ? childrenTop : oldFirst.getTop());  
  158.                 } else {  
  159.                     sel = fillSpecific(0, childrenTop);  
  160.                 }  
  161.             }  
  162.             break;  
  163.         }  
  164.         // Flush any cached views that did not get reused above  
  165.         recycleBin.scrapActiveViews();  
  166.         if (sel != null) {  
  167.             // the current selected item should get focus if items  
  168.             // are focusable  
  169.             if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {  
  170.                 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&  
  171.                         focusLayoutRestoreView.requestFocus()) || sel.requestFocus();  
  172.                 if (!focusWasTaken) {  
  173.                     // selected item didn't take focus, fine, but still want  
  174.                     // to make sure something else outside of the selected view  
  175.                     // has focus  
  176.                     final View focused = getFocusedChild();  
  177.                     if (focused != null) {  
  178.                         focused.clearFocus();  
  179.                     }  
  180.                     positionSelector(sel);  
  181.                 } else {  
  182.                     sel.setSelected(false);  
  183.                     mSelectorRect.setEmpty();  
  184.                 }  
  185.             } else {  
  186.                 positionSelector(sel);  
  187.             }  
  188.             mSelectedTop = sel.getTop();  
  189.         } else {  
  190.             if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {  
  191.                 View child = getChildAt(mMotionPosition - mFirstPosition);  
  192.                 if (child != null) positionSelector(child);  
  193.             } else {  
  194.                 mSelectedTop = 0;  
  195.                 mSelectorRect.setEmpty();  
  196.             }  
  197.             // even if there is not selected position, we may need to restore  
  198.             // focus (i.e. something focusable in touch mode)  
  199.             if (hasFocus() && focusLayoutRestoreView != null) {  
  200.                 focusLayoutRestoreView.requestFocus();  
  201.             }  
  202.         }  
  203.         // tell focus view we are done mucking with it, if it is still in  
  204.         // our view hierarchy.  
  205.         if (focusLayoutRestoreView != null  
  206.                 && focusLayoutRestoreView.getWindowToken() != null) {  
  207.             focusLayoutRestoreView.onFinishTemporaryDetach();  
  208.         }  
  209.         mLayoutMode = LAYOUT_NORMAL;  
  210.         mDataChanged = false;  
  211.         mNeedSync = false;  
  212.         setNextSelectedPositionInt(mSelectedPosition);  
  213.         updateScrollIndicators();  
  214.         if (mItemCount > 0) {  
  215.             checkSelectionChanged();  
  216.         }  
  217.         invokeOnItemScrollListener();  
  218.     } finally {  
  219.         if (!blockLayoutRequests) {  
  220.             mBlockLayoutRequests = false;  
  221.         }  
  222.     }  
  223. }  

這段代碼比較長,咱們挑重點的看。首先能夠肯定的是,ListView當中目前尚未任何子View,數據都仍是由Adapter管理的,並無展現到界面上,所以第19行getChildCount()方法獲得的值確定是0。接着在第81行會根據dataChanged這個布爾型的值來判斷執行邏輯,dataChanged只有在數據源發生改變的狀況下才會變成true,其它狀況都是false,所以這裏會進入到第90行的執行邏輯,調用RecycleBin的fillActiveViews()方法。按理來講,調用fillActiveViews()方法是爲了將ListView的子View進行緩存的,但是目前ListView中尚未任何的子View,所以這一行暫時還起不了任何做用。

接下來在第114行會根據mLayoutMode的值來決定佈局模式,默認狀況下都是普通模式LAYOUT_NORMAL,所以會進入到第140行的default語句當中。而下面又會緊接着進行兩次if判斷,childCount目前是等於0的,而且默認的佈局順序是從上往下,所以會進入到第145行的fillFromTop()方法,咱們跟進去瞧一瞧:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the list from top to bottom, starting with mFirstPosition 
  3.  * 
  4.  * @param nextTop The location where the top of the first item should be 
  5.  *        drawn 
  6.  * 
  7.  * @return The view that is currently selected 
  8.  */  
  9. private View fillFromTop(int nextTop) {  
  10.     mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);  
  11.     mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);  
  12.     if (mFirstPosition < 0) {  
  13.         mFirstPosition = 0;  
  14.     }  
  15.     return fillDown(mFirstPosition, nextTop);  
  16. }  

從這個方法的註釋中能夠看出,它所負責的主要任務就是從mFirstPosition開始,自頂至底去填充ListView。而這個方法自己並無什麼邏輯,就是判斷了一下mFirstPosition值的合法性,而後調用fillDown()方法,那麼咱們就有理由能夠猜想,填充ListView的操做是在fillDown()方法中完成的。進入fillDown()方法,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the list from pos down to the end of the list view. 
  3.  * 
  4.  * @param pos The first position to put in the list 
  5.  * 
  6.  * @param nextTop The location where the top of the item associated with pos 
  7.  *        should be drawn 
  8.  * 
  9.  * @return The view that is currently selected, if it happens to be in the 
  10.  *         range that we draw. 
  11.  */  
  12. private View fillDown(int pos, int nextTop) {  
  13.     View selectedView = null;  
  14.     int end = (getBottom() - getTop()) - mListPadding.bottom;  
  15.     while (nextTop < end && pos < mItemCount) {  
  16.         // is this the selected item?  
  17.         boolean selected = pos == mSelectedPosition;  
  18.         View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);  
  19.         nextTop = child.getBottom() + mDividerHeight;  
  20.         if (selected) {  
  21.             selectedView = child;  
  22.         }  
  23.         pos++;  
  24.     }  
  25.     return selectedView;  
  26. }  

能夠看到,這裏使用了一個while循環來執行重複邏輯,一開始nextTop的值是第一個子元素頂部距離整個ListView頂部的像素值,pos則是剛剛傳入的mFirstPosition的值,而end是ListView底部減去頂部所得的像素值,mItemCount則是Adapter中的元素數量。所以一開始的狀況下nextTop一定是小於end值的,而且pos也是小於mItemCount值的。那麼每執行一次while循環,pos的值都會加1,而且nextTop也會增長,當nextTop大於等於end時,也就是子元素已經超出當前屏幕了,或者pos大於等於mItemCount時,也就是全部Adapter中的元素都被遍歷結束了,就會跳出while循環。

那麼while循環當中又作了什麼事情呢?值得讓人留意的就是第18行調用的makeAndAddView()方法,進入到這個方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

這裏在第19行嘗試從RecycleBin當中快速獲取一個active view,不過很遺憾的是目前RecycleBin當中尚未緩存任何的View,因此這裏獲得的值確定是null。那麼取得了null以後就會繼續向下運行,到第28行會調用obtainView()方法來再次嘗試獲取一個View,此次的obtainView()方法是能夠保證必定返回一個View的,因而下面馬上將獲取到的View傳入到了setupChild()方法當中。那麼obtainView()內部究竟是怎麼工做的呢?咱們先進入到這個方法裏面看一下:

[java]  view plain  copy
 
  1. /** 
  2.  * Get a view and have it show the data associated with the specified 
  3.  * position. This is called when we have already discovered that the view is 
  4.  * not available for reuse in the recycle bin. The only choices left are 
  5.  * converting an old view or making a new one. 
  6.  *  
  7.  * @param position 
  8.  *            The position to display 
  9.  * @param isScrap 
  10.  *            Array of at least 1 boolean, the first entry will become true 
  11.  *            if the returned view was taken from the scrap heap, false if 
  12.  *            otherwise. 
  13.  *  
  14.  * @return A view displaying the data associated with the specified position 
  15.  */  
  16. View obtainView(int position, boolean[] isScrap) {  
  17.     isScrap[0] = false;  
  18.     View scrapView;  
  19.     scrapView = mRecycler.getScrapView(position);  
  20.     View child;  
  21.     if (scrapView != null) {  
  22.         child = mAdapter.getView(position, scrapView, this);  
  23.         if (child != scrapView) {  
  24.             mRecycler.addScrapView(scrapView);  
  25.             if (mCacheColorHint != 0) {  
  26.                 child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  27.             }  
  28.         } else {  
  29.             isScrap[0] = true;  
  30.             dispatchFinishTemporaryDetach(child);  
  31.         }  
  32.     } else {  
  33.         child = mAdapter.getView(position, null, this);  
  34.         if (mCacheColorHint != 0) {  
  35.             child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  36.         }  
  37.     }  
  38.     return child;  
  39. }  

obtainView()方法中的代碼並很少,但卻包含了很是很是重要的邏輯,不誇張的說,整個ListView中最重要的內容可能就在這個方法裏了。那麼咱們仍是按照執行流程來看,在第19行代碼中調用了RecycleBin的getScrapView()方法來嘗試獲取一個廢棄緩存中的View,一樣的道理,這裏確定是獲取不到的,getScrapView()方法會返回一個null。這時該怎麼辦呢?沒有關係,代碼會執行到第33行,調用mAdapter的getView()方法來去獲取一個View。那麼mAdapter是什麼呢?固然就是當前ListView關聯的適配器了。而getView()方法又是什麼呢?還用說嗎,這個就是咱們平時使用ListView時最最常常重寫的一個方法了,這裏getView()方法中傳入了三個參數,分別是position,null和this。

 

那麼咱們平時寫ListView的Adapter時,getView()方法一般會怎麼寫呢?這裏我舉個簡單的例子:

[java]  view plain  copy
 
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     Fruit fruit = getItem(position);  
  4.     View view;  
  5.     if (convertView == null) {  
  6.         view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  7.     } else {  
  8.         view = convertView;  
  9.     }  
  10.     ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  11.     TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  12.     fruitImage.setImageResource(fruit.getImageId());  
  13.     fruitName.setText(fruit.getName());  
  14.     return view;  
  15. }  

getView()方法接受的三個參數,第一個參數position表明當前子元素的的位置,咱們能夠經過具體的位置來獲取與其相關的數據。第二個參數convertView,剛纔傳入的是null,說明沒有convertView能夠利用,所以咱們會調用LayoutInflater的inflate()方法來去加載一個佈局。接下來會對這個view進行一些屬性和值的設定,最後將view返回。

 

那麼這個View也會做爲obtainView()的結果進行返回,並最終傳入到setupChild()方法當中。其實也就是說,第一次layout過程中,全部的子View都是調用LayoutInflater的inflate()方法加載出來的,這樣就會相對比較耗時,可是不用擔憂,後面就不會再有這種狀況了,那麼咱們繼續往下看:

[java]  view plain  copy
 
  1. /** 
  2.  * Add a view as a child and make sure it is measured (if necessary) and 
  3.  * positioned properly. 
  4.  * 
  5.  * @param child The view to add 
  6.  * @param position The position of this child 
  7.  * @param y The y position relative to which this view will be positioned 
  8.  * @param flowDown If true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @param recycled Has this view been pulled from the recycle bin? If so it 
  13.  *        does not need to be remeasured. 
  14.  */  
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,  
  16.         boolean selected, boolean recycled) {  
  17.     final boolean isSelected = selected && shouldShowSelector();  
  18.     final boolean updateChildSelected = isSelected != child.isSelected();  
  19.     final int mode = mTouchMode;  
  20.     final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&  
  21.             mMotionPosition == position;  
  22.     final boolean updateChildPressed = isPressed != child.isPressed();  
  23.     final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  
  24.     // Respect layout params that are already in the view. Otherwise make some up...  
  25.     // noinspection unchecked  
  26.     AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();  
  27.     if (p == null) {  
  28.         p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  29.                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);  
  30.     }  
  31.     p.viewType = mAdapter.getItemViewType(position);  
  32.     if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&  
  33.             p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {  
  34.         attachViewToParent(child, flowDown ? -1 : 0, p);  
  35.     } else {  
  36.         p.forceAdd = false;  
  37.         if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  38.             p.recycledHeaderFooter = true;  
  39.         }  
  40.         addViewInLayout(child, flowDown ? -1 : 0, p, true);  
  41.     }  
  42.     if (updateChildSelected) {  
  43.         child.setSelected(isSelected);  
  44.     }  
  45.     if (updateChildPressed) {  
  46.         child.setPressed(isPressed);  
  47.     }  
  48.     if (needToMeasure) {  
  49.         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
  50.                 mListPadding.left + mListPadding.right, p.width);  
  51.         int lpHeight = p.height;  
  52.         int childHeightSpec;  
  53.         if (lpHeight > 0) {  
  54.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  55.         } else {  
  56.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  57.         }  
  58.         child.measure(childWidthSpec, childHeightSpec);  
  59.     } else {  
  60.         cleanupLayoutState(child);  
  61.     }  
  62.     final int w = child.getMeasuredWidth();  
  63.     final int h = child.getMeasuredHeight();  
  64.     final int childTop = flowDown ? y : y - h;  
  65.     if (needToMeasure) {  
  66.         final int childRight = childrenLeft + w;  
  67.         final int childBottom = childTop + h;  
  68.         child.layout(childrenLeft, childTop, childRight, childBottom);  
  69.     } else {  
  70.         child.offsetLeftAndRight(childrenLeft - child.getLeft());  
  71.         child.offsetTopAndBottom(childTop - child.getTop());  
  72.     }  
  73.     if (mCachingStarted && !child.isDrawingCacheEnabled()) {  
  74.         child.setDrawingCacheEnabled(true);  
  75.     }  
  76. }  

setupChild()方法當中的代碼雖然比較多,可是咱們只看核心代碼的話就很是簡單了,剛纔調用obtainView()方法獲取到的子元素View,這裏在第40行調用了addViewInLayout()方法將它添加到了ListView當中。那麼根據fillDown()方法中的while循環,會讓子元素View將整個ListView控件填滿而後就跳出,也就是說即便咱們的Adapter中有一千條數據,ListView也只會加載第一屏的數據,剩下的數據反正目前在屏幕上也看不到,因此不會去作多餘的加載工做,這樣就能夠保證ListView中的內容可以迅速展現到屏幕上。

 

那麼到此爲止,第一次Layout過程結束。

第二次Layout

 

雖然我在源碼中並無找出具體的緣由,但若是你本身作一下實驗的話就會發現,即便是一個再簡單的View,在展現到界面上以前都會經歷至少兩次onMeasure()和兩次onLayout()的過程。其實這只是一個很小的細節,平時對咱們影響並不大,由於無論是onMeasure()或者onLayout()幾回,反正都是執行的相同的邏輯,咱們並不須要進行過多關心。可是在ListView中狀況就不同了,由於這就意味着layoutChildren()過程會執行兩次,而這個過程中涉及到向ListView中添加子元素,若是相同的邏輯執行兩遍的話,那麼ListView中就會存在一份重複的數據了。所以ListView在layoutChildren()過程中作了第二次Layout的邏輯處理,很是巧妙地解決了這個問題,下面咱們就來分析一下第二次Layout的過程。

其實第二次Layout和第一次Layout的基本流程是差很少的,那麼咱們仍是從layoutChildren()方法開始看起:

[java]  view plain  copy
 
  1. @Override  
  2. protected void layoutChildren() {  
  3.     final boolean blockLayoutRequests = mBlockLayoutRequests;  
  4.     if (!blockLayoutRequests) {  
  5.         mBlockLayoutRequests = true;  
  6.     } else {  
  7.         return;  
  8.     }  
  9.     try {  
  10.         super.layoutChildren();  
  11.         invalidate();  
  12.         if (mAdapter == null) {  
  13.             resetList();  
  14.             invokeOnItemScrollListener();  
  15.             return;  
  16.         }  
  17.         int childrenTop = mListPadding.top;  
  18.         int childrenBottom = getBottom() - getTop() - mListPadding.bottom;  
  19.         int childCount = getChildCount();  
  20.         int index = 0;  
  21.         int delta = 0;  
  22.         View sel;  
  23.         View oldSel = null;  
  24.         View oldFirst = null;  
  25.         View newSel = null;  
  26.         View focusLayoutRestoreView = null;  
  27.         // Remember stuff we will need down below  
  28.         switch (mLayoutMode) {  
  29.         case LAYOUT_SET_SELECTION:  
  30.             index = mNextSelectedPosition - mFirstPosition;  
  31.             if (index >= 0 && index < childCount) {  
  32.                 newSel = getChildAt(index);  
  33.             }  
  34.             break;  
  35.         case LAYOUT_FORCE_TOP:  
  36.         case LAYOUT_FORCE_BOTTOM:  
  37.         case LAYOUT_SPECIFIC:  
  38.         case LAYOUT_SYNC:  
  39.             break;  
  40.         case LAYOUT_MOVE_SELECTION:  
  41.         default:  
  42.             // Remember the previously selected view  
  43.             index = mSelectedPosition - mFirstPosition;  
  44.             if (index >= 0 && index < childCount) {  
  45.                 oldSel = getChildAt(index);  
  46.             }  
  47.             // Remember the previous first child  
  48.             oldFirst = getChildAt(0);  
  49.             if (mNextSelectedPosition >= 0) {  
  50.                 delta = mNextSelectedPosition - mSelectedPosition;  
  51.             }  
  52.             // Caution: newSel might be null  
  53.             newSel = getChildAt(index + delta);  
  54.         }  
  55.         boolean dataChanged = mDataChanged;  
  56.         if (dataChanged) {  
  57.             handleDataChanged();  
  58.         }  
  59.         // Handle the empty set by removing all views that are visible  
  60.         // and calling it a day  
  61.         if (mItemCount == 0) {  
  62.             resetList();  
  63.             invokeOnItemScrollListener();  
  64.             return;  
  65.         } else if (mItemCount != mAdapter.getCount()) {  
  66.             throw new IllegalStateException("The content of the adapter has changed but "  
  67.                     + "ListView did not receive a notification. Make sure the content of "  
  68.                     + "your adapter is not modified from a background thread, but only "  
  69.                     + "from the UI thread. [in ListView(" + getId() + ", " + getClass()   
  70.                     + ") with Adapter(" + mAdapter.getClass() + ")]");  
  71.         }  
  72.         setSelectedPositionInt(mNextSelectedPosition);  
  73.         // Pull all children into the RecycleBin.  
  74.         // These views will be reused if possible  
  75.         final int firstPosition = mFirstPosition;  
  76.         final RecycleBin recycleBin = mRecycler;  
  77.         // reset the focus restoration  
  78.         View focusLayoutRestoreDirectChild = null;  
  79.         // Don't put header or footer views into the Recycler. Those are  
  80.         // already cached in mHeaderViews;  
  81.         if (dataChanged) {  
  82.             for (int i = 0; i < childCount; i++) {  
  83.                 recycleBin.addScrapView(getChildAt(i));  
  84.                 if (ViewDebug.TRACE_RECYCLER) {  
  85.                     ViewDebug.trace(getChildAt(i),  
  86.                             ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);  
  87.                 }  
  88.             }  
  89.         } else {  
  90.             recycleBin.fillActiveViews(childCount, firstPosition);  
  91.         }  
  92.         // take focus back to us temporarily to avoid the eventual  
  93.         // call to clear focus when removing the focused child below  
  94.         // from messing things up when ViewRoot assigns focus back  
  95.         // to someone else  
  96.         final View focusedChild = getFocusedChild();  
  97.         if (focusedChild != null) {  
  98.             // TODO: in some cases focusedChild.getParent() == null  
  99.             // we can remember the focused view to restore after relayout if the  
  100.             // data hasn't changed, or if the focused position is a header or footer  
  101.             if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {  
  102.                 focusLayoutRestoreDirectChild = focusedChild;  
  103.                 // remember the specific view that had focus  
  104.                 focusLayoutRestoreView = findFocus();  
  105.                 if (focusLayoutRestoreView != null) {  
  106.                     // tell it we are going to mess with it  
  107.                     focusLayoutRestoreView.onStartTemporaryDetach();  
  108.                 }  
  109.             }  
  110.             requestFocus();  
  111.         }  
  112.         // Clear out old views  
  113.         detachAllViewsFromParent();  
  114.         switch (mLayoutMode) {  
  115.         case LAYOUT_SET_SELECTION:  
  116.             if (newSel != null) {  
  117.                 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);  
  118.             } else {  
  119.                 sel = fillFromMiddle(childrenTop, childrenBottom);  
  120.             }  
  121.             break;  
  122.         case LAYOUT_SYNC:  
  123.             sel = fillSpecific(mSyncPosition, mSpecificTop);  
  124.             break;  
  125.         case LAYOUT_FORCE_BOTTOM:  
  126.             sel = fillUp(mItemCount - 1, childrenBottom);  
  127.             adjustViewsUpOrDown();  
  128.             break;  
  129.         case LAYOUT_FORCE_TOP:  
  130.             mFirstPosition = 0;  
  131.             sel = fillFromTop(childrenTop);  
  132.             adjustViewsUpOrDown();  
  133.             break;  
  134.         case LAYOUT_SPECIFIC:  
  135.             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);  
  136.             break;  
  137.         case LAYOUT_MOVE_SELECTION:  
  138.             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);  
  139.             break;  
  140.         default:  
  141.             if (childCount == 0) {  
  142.                 if (!mStackFromBottom) {  
  143.                     final int position = lookForSelectablePosition(0, true);  
  144.                     setSelectedPositionInt(position);  
  145.                     sel = fillFromTop(childrenTop);  
  146.                 } else {  
  147.                     final int position = lookForSelectablePosition(mItemCount - 1, false);  
  148.                     setSelectedPositionInt(position);  
  149.                     sel = fillUp(mItemCount - 1, childrenBottom);  
  150.                 }  
  151.             } else {  
  152.                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {  
  153.                     sel = fillSpecific(mSelectedPosition,  
  154.                             oldSel == null ? childrenTop : oldSel.getTop());  
  155.                 } else if (mFirstPosition < mItemCount) {  
  156.                     sel = fillSpecific(mFirstPosition,  
  157.                             oldFirst == null ? childrenTop : oldFirst.getTop());  
  158.                 } else {  
  159.                     sel = fillSpecific(0, childrenTop);  
  160.                 }  
  161.             }  
  162.             break;  
  163.         }  
  164.         // Flush any cached views that did not get reused above  
  165.         recycleBin.scrapActiveViews();  
  166.         if (sel != null) {  
  167.             // the current selected item should get focus if items  
  168.             // are focusable  
  169.             if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {  
  170.                 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&  
  171.                         focusLayoutRestoreView.requestFocus()) || sel.requestFocus();  
  172.                 if (!focusWasTaken) {  
  173.                     // selected item didn't take focus, fine, but still want  
  174.                     // to make sure something else outside of the selected view  
  175.                     // has focus  
  176.                     final View focused = getFocusedChild();  
  177.                     if (focused != null) {  
  178.                         focused.clearFocus();  
  179.                     }  
  180.                     positionSelector(sel);  
  181.                 } else {  
  182.                     sel.setSelected(false);  
  183.                     mSelectorRect.setEmpty();  
  184.                 }  
  185.             } else {  
  186.                 positionSelector(sel);  
  187.             }  
  188.             mSelectedTop = sel.getTop();  
  189.         } else {  
  190.             if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {  
  191.                 View child = getChildAt(mMotionPosition - mFirstPosition);  
  192.                 if (child != null) positionSelector(child);  
  193.             } else {  
  194.                 mSelectedTop = 0;  
  195.                 mSelectorRect.setEmpty();  
  196.             }  
  197.             // even if there is not selected position, we may need to restore  
  198.             // focus (i.e. something focusable in touch mode)  
  199.             if (hasFocus() && focusLayoutRestoreView != null) {  
  200.                 focusLayoutRestoreView.requestFocus();  
  201.             }  
  202.         }  
  203.         // tell focus view we are done mucking with it, if it is still in  
  204.         // our view hierarchy.  
  205.         if (focusLayoutRestoreView != null  
  206.                 && focusLayoutRestoreView.getWindowToken() != null) {  
  207.             focusLayoutRestoreView.onFinishTemporaryDetach();  
  208.         }  
  209.         mLayoutMode = LAYOUT_NORMAL;  
  210.         mDataChanged = false;  
  211.         mNeedSync = false;  
  212.         setNextSelectedPositionInt(mSelectedPosition);  
  213.         updateScrollIndicators();  
  214.         if (mItemCount > 0) {  
  215.             checkSelectionChanged();  
  216.         }  
  217.         invokeOnItemScrollListener();  
  218.     } finally {  
  219.         if (!blockLayoutRequests) {  
  220.             mBlockLayoutRequests = false;  
  221.         }  
  222.     }  
  223. }  

一樣仍是在第19行,調用getChildCount()方法來獲取子View的數量,只不過如今獲得的值不會再是0了,而是ListView中一屏能夠顯示的子View數量,由於咱們剛剛在第一次Layout過程中向ListView添加了這麼多的子View。下面在第90行調用了RecycleBin的fillActiveViews()方法,此次效果可就不同了,由於目前ListView中已經有子View了,這樣全部的子View都會被緩存到RecycleBin的mActiveViews數組當中,後面將會用到它們。

接下來將會是很是很是重要的一個操做,在第113行調用了detachAllViewsFromParent()方法。這個方法會將全部ListView當中的子View所有清除掉,從而保證第二次Layout過程不會產生一份重複的數據。那有的朋友可能會問了,這樣把已經加載好的View又清除掉,待會還要再從新加載一遍,這不是嚴重影響效率嗎?不用擔憂,還記得咱們剛剛調用了RecycleBin的fillActiveViews()方法來緩存子View嗎,待會兒將會直接使用這些緩存好的View來進行加載,而並不會從新執行一遍inflate過程,所以效率方面並不會有什麼明顯的影響。

那麼咱們接着看,在第141行的判斷邏輯當中,因爲再也不等於0了,所以會進入到else語句當中。而else語句中又有三個邏輯判斷,第一個邏輯判斷不成立,由於默認狀況下咱們沒有選中任何子元素,mSelectedPosition應該等於-1。第二個邏輯判斷一般是成立的,由於mFirstPosition的值一開始是等於0的,只要adapter中的數據大於0條件就成立。那麼進入到fillSpecific()方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Put a specific item at a specific location on the screen and then build 
  3.  * up and down from there. 
  4.  * 
  5.  * @param position The reference view to use as the starting point 
  6.  * @param top Pixel offset from the top of this view to the top of the 
  7.  *        reference view. 
  8.  * 
  9.  * @return The selected view, or null if the selected view is outside the 
  10.  *         visible area. 
  11.  */  
  12. private View fillSpecific(int position, int top) {  
  13.     boolean tempIsSelected = position == mSelectedPosition;  
  14.     View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);  
  15.     // Possibly changed again in fillUp if we add rows above this one.  
  16.     mFirstPosition = position;  
  17.     View above;  
  18.     View below;  
  19.     final int dividerHeight = mDividerHeight;  
  20.     if (!mStackFromBottom) {  
  21.         above = fillUp(position - 1, temp.getTop() - dividerHeight);  
  22.         // This will correct for the top of the first view not touching the top of the list  
  23.         adjustViewsUpOrDown();  
  24.         below = fillDown(position + 1, temp.getBottom() + dividerHeight);  
  25.         int childCount = getChildCount();  
  26.         if (childCount > 0) {  
  27.             correctTooHigh(childCount);  
  28.         }  
  29.     } else {  
  30.         below = fillDown(position + 1, temp.getBottom() + dividerHeight);  
  31.         // This will correct for the bottom of the last view not touching the bottom of the list  
  32.         adjustViewsUpOrDown();  
  33.         above = fillUp(position - 1, temp.getTop() - dividerHeight);  
  34.         int childCount = getChildCount();  
  35.         if (childCount > 0) {  
  36.              correctTooLow(childCount);  
  37.         }  
  38.     }  
  39.     if (tempIsSelected) {  
  40.         return temp;  
  41.     } else if (above != null) {  
  42.         return above;  
  43.     } else {  
  44.         return below;  
  45.     }  
  46. }  

fillSpecific()這算是一個新方法了,不過其實它和fillUp()、fillDown()方法功能也是差很少的,主要的區別在於,fillSpecific()方法會優先將指定位置的子View先加載到屏幕上,而後再加載該子View往上以及往下的其它子View。那麼因爲這裏咱們傳入的position就是第一個子View的位置,因而fillSpecific()方法的做用就基本上和fillDown()方法是差很少的了,這裏咱們就不去關注太多它的細節,而是將精力放在makeAndAddView()方法上面。再次回到makeAndAddView()方法,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

仍然仍是在第19行嘗試從RecycleBin當中獲取Active View,然而此次就必定能夠獲取到了,由於前面咱們調用了RecycleBin的fillActiveViews()方法來緩存子View。那麼既然如此,就不會再進入到第28行的obtainView()方法,而是會直接進入setupChild()方法當中,這樣也省去了不少時間,由於若是在obtainView()方法中又要去infalte佈局的話,那麼ListView的初始加載效率就大大下降了。

注意在第23行,setupChild()方法的最後一個參數傳入的是true,這個參數代表當前的View是以前被回收過的,那麼咱們再次回到setupChild()方法當中:

[java]  view plain  copy
 
  1. /** 
  2.  * Add a view as a child and make sure it is measured (if necessary) and 
  3.  * positioned properly. 
  4.  * 
  5.  * @param child The view to add 
  6.  * @param position The position of this child 
  7.  * @param y The y position relative to which this view will be positioned 
  8.  * @param flowDown If true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @param recycled Has this view been pulled from the recycle bin? If so it 
  13.  *        does not need to be remeasured. 
  14.  */  
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,  
  16.         boolean selected, boolean recycled) {  
  17.     final boolean isSelected = selected && shouldShowSelector();  
  18.     final boolean updateChildSelected = isSelected != child.isSelected();  
  19.     final int mode = mTouchMode;  
  20.     final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&  
  21.             mMotionPosition == position;  
  22.     final boolean updateChildPressed = isPressed != child.isPressed();  
  23.     final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  
  24.     // Respect layout params that are already in the view. Otherwise make some up...  
  25.     // noinspection unchecked  
  26.     AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();  
  27.     if (p == null) {  
  28.         p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  29.                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);  
  30.     }  
  31.     p.viewType = mAdapter.getItemViewType(position);  
  32.     if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&  
  33.             p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {  
  34.         attachViewToParent(child, flowDown ? -1 : 0, p);  
  35.     } else {  
  36.         p.forceAdd = false;  
  37.         if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  38.             p.recycledHeaderFooter = true;  
  39.         }  
  40.         addViewInLayout(child, flowDown ? -1 : 0, p, true);  
  41.     }  
  42.     if (updateChildSelected) {  
  43.         child.setSelected(isSelected);  
  44.     }  
  45.     if (updateChildPressed) {  
  46.         child.setPressed(isPressed);  
  47.     }  
  48.     if (needToMeasure) {  
  49.         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
  50.                 mListPadding.left + mListPadding.right, p.width);  
  51.         int lpHeight = p.height;  
  52.         int childHeightSpec;  
  53.         if (lpHeight > 0) {  
  54.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  55.         } else {  
  56.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  57.         }  
  58.         child.measure(childWidthSpec, childHeightSpec);  
  59.     } else {  
  60.         cleanupLayoutState(child);  
  61.     }  
  62.     final int w = child.getMeasuredWidth();  
  63.     final int h = child.getMeasuredHeight();  
  64.     final int childTop = flowDown ? y : y - h;  
  65.     if (needToMeasure) {  
  66.         final int childRight = childrenLeft + w;  
  67.         final int childBottom = childTop + h;  
  68.         child.layout(childrenLeft, childTop, childRight, childBottom);  
  69.     } else {  
  70.         child.offsetLeftAndRight(childrenLeft - child.getLeft());  
  71.         child.offsetTopAndBottom(childTop - child.getTop());  
  72.     }  
  73.     if (mCachingStarted && !child.isDrawingCacheEnabled()) {  
  74.         child.setDrawingCacheEnabled(true);  
  75.     }  
  76. }  

能夠看到,setupChild()方法的最後一個參數是recycled,而後在第32行會對這個變量進行判斷,因爲recycled如今是true,因此會執行attachViewToParent()方法,而第一次Layout過程則是執行的else語句中的addViewInLayout()方法。這兩個方法最大的區別在於,若是咱們須要向ViewGroup中添加一個新的子View,應該調用addViewInLayout()方法,而若是是想要將一個以前detach的View從新attach到ViewGroup上,就應該調用attachViewToParent()方法。那麼因爲前面在layoutChildren()方法當中調用了detachAllViewsFromParent()方法,這樣ListView中全部的子View都是處於detach狀態的,因此這裏attachViewToParent()方法是正確的選擇。

 

經歷了這樣一個detach又attach的過程,ListView中全部的子View又均可以正常顯示出來了,那麼第二次Layout過程結束。

滑動加載更多數據

經歷了兩次Layout過程,雖然說咱們已經能夠在ListView中看到內容了,然而關於ListView最神奇的部分咱們卻尚未接觸到,由於目前ListView中只是加載並顯示了第一屏的數據而已。好比說咱們的Adapter當中有1000條數據,可是第一屏只顯示了10條,ListView中也只有10個子View而已,那麼剩下的990是怎樣工做並顯示到界面上的呢?這就要看一下ListView滑動部分的源碼了,由於咱們是經過手指滑動來顯示更多數據的。

因爲滑動部分的機制是屬於通用型的,即ListView和GridView都會使用一樣的機制,所以這部分代碼就確定是寫在AbsListView當中的了。那麼監聽觸控事件是在onTouchEvent()方法當中進行的,咱們就來看一下AbsListView中的這個方法:

[java]  view plain  copy
 
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent ev) {  
  3.     if (!isEnabled()) {  
  4.         // A disabled view that is clickable still consumes the touch  
  5.         // events, it just doesn't respond to them.  
  6.         return isClickable() || isLongClickable();  
  7.     }  
  8.     final int action = ev.getAction();  
  9.     View v;  
  10.     int deltaY;  
  11.     if (mVelocityTracker == null) {  
  12.         mVelocityTracker = VelocityTracker.obtain();  
  13.     }  
  14.     mVelocityTracker.addMovement(ev);  
  15.     switch (action & MotionEvent.ACTION_MASK) {  
  16.     case MotionEvent.ACTION_DOWN: {  
  17.         mActivePointerId = ev.getPointerId(0);  
  18.         final int x = (int) ev.getX();  
  19.         final int y = (int) ev.getY();  
  20.         int motionPosition = pointToPosition(x, y);  
  21.         if (!mDataChanged) {  
  22.             if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)  
  23.                     && (getAdapter().isEnabled(motionPosition))) {  
  24.                 // User clicked on an actual view (and was not stopping a  
  25.                 // fling). It might be a  
  26.                 // click or a scroll. Assume it is a click until proven  
  27.                 // otherwise  
  28.                 mTouchMode = TOUCH_MODE_DOWN;  
  29.                 // FIXME Debounce  
  30.                 if (mPendingCheckForTap == null) {  
  31.                     mPendingCheckForTap = new CheckForTap();  
  32.                 }  
  33.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  34.             } else {  
  35.                 if (ev.getEdgeFlags() != 0 && motionPosition < 0) {  
  36.                     // If we couldn't find a view to click on, but the down  
  37.                     // event was touching  
  38.                     // the edge, we will bail out and try again. This allows  
  39.                     // the edge correcting  
  40.                     // code in ViewRoot to try to find a nearby view to  
  41.                     // select  
  42.                     return false;  
  43.                 }  
  44.   
  45.                 if (mTouchMode == TOUCH_MODE_FLING) {  
  46.                     // Stopped a fling. It is a scroll.  
  47.                     createScrollingCache();  
  48.                     mTouchMode = TOUCH_MODE_SCROLL;  
  49.                     mMotionCorrection = 0;  
  50.                     motionPosition = findMotionRow(y);  
  51.                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);  
  52.                 }  
  53.             }  
  54.         }  
  55.         if (motionPosition >= 0) {  
  56.             // Remember where the motion event started  
  57.             v = getChildAt(motionPosition - mFirstPosition);  
  58.             mMotionViewOriginalTop = v.getTop();  
  59.         }  
  60.         mMotionX = x;  
  61.         mMotionY = y;  
  62.         mMotionPosition = motionPosition;  
  63.         mLastY = Integer.MIN_VALUE;  
  64.         break;  
  65.     }  
  66.     case MotionEvent.ACTION_MOVE: {  
  67.         final int pointerIndex = ev.findPointerIndex(mActivePointerId);  
  68.         final int y = (int) ev.getY(pointerIndex);  
  69.         deltaY = y - mMotionY;  
  70.         switch (mTouchMode) {  
  71.         case TOUCH_MODE_DOWN:  
  72.         case TOUCH_MODE_TAP:  
  73.         case TOUCH_MODE_DONE_WAITING:  
  74.             // Check if we have moved far enough that it looks more like a  
  75.             // scroll than a tap  
  76.             startScrollIfNeeded(deltaY);  
  77.             break;  
  78.         case TOUCH_MODE_SCROLL:  
  79.             if (PROFILE_SCROLLING) {  
  80.                 if (!mScrollProfilingStarted) {  
  81.                     Debug.startMethodTracing("AbsListViewScroll");  
  82.                     mScrollProfilingStarted = true;  
  83.                 }  
  84.             }  
  85.             if (y != mLastY) {  
  86.                 deltaY -= mMotionCorrection;  
  87.                 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;  
  88.                 // No need to do all this work if we're not going to move  
  89.                 // anyway  
  90.                 boolean atEdge = false;  
  91.                 if (incrementalDeltaY != 0) {  
  92.                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);  
  93.                 }  
  94.                 // Check to see if we have bumped into the scroll limit  
  95.                 if (atEdge && getChildCount() > 0) {  
  96.                     // Treat this like we're starting a new scroll from the  
  97.                     // current  
  98.                     // position. This will let the user start scrolling back  
  99.                     // into  
  100.                     // content immediately rather than needing to scroll  
  101.                     // back to the  
  102.                     // point where they hit the limit first.  
  103.                     int motionPosition = findMotionRow(y);  
  104.                     if (motionPosition >= 0) {  
  105.                         final View motionView = getChildAt(motionPosition - mFirstPosition);  
  106.                         mMotionViewOriginalTop = motionView.getTop();  
  107.                     }  
  108.                     mMotionY = y;  
  109.                     mMotionPosition = motionPosition;  
  110.                     invalidate();  
  111.                 }  
  112.                 mLastY = y;  
  113.             }  
  114.             break;  
  115.         }  
  116.         break;  
  117.     }  
  118.     case MotionEvent.ACTION_UP: {  
  119.         switch (mTouchMode) {  
  120.         case TOUCH_MODE_DOWN:  
  121.         case TOUCH_MODE_TAP:  
  122.         case TOUCH_MODE_DONE_WAITING:  
  123.             final int motionPosition = mMotionPosition;  
  124.             final View child = getChildAt(motionPosition - mFirstPosition);  
  125.             if (child != null && !child.hasFocusable()) {  
  126.                 if (mTouchMode != TOUCH_MODE_DOWN) {  
  127.                     child.setPressed(false);  
  128.                 }  
  129.                 if (mPerformClick == null) {  
  130.                     mPerformClick = new PerformClick();  
  131.                 }  
  132.                 final AbsListView.PerformClick performClick = mPerformClick;  
  133.                 performClick.mChild = child;  
  134.                 performClick.mClickMotionPosition = motionPosition;  
  135.                 performClick.rememberWindowAttachCount();  
  136.                 mResurrectToPosition = motionPosition;  
  137.                 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {  
  138.                     final Handler handler = getHandler();  
  139.                     if (handler != null) {  
  140.                         handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap  
  141.                                 : mPendingCheckForLongPress);  
  142.                     }  
  143.                     mLayoutMode = LAYOUT_NORMAL;  
  144.                     if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {  
  145.                         mTouchMode = TOUCH_MODE_TAP;  
  146.                         setSelectedPositionInt(mMotionPosition);  
  147.                         layoutChildren();  
  148.                         child.setPressed(true);  
  149.                         positionSelector(child);  
  150.                         setPressed(true);  
  151.                         if (mSelector != null) {  
  152.                             Drawable d = mSelector.getCurrent();  
  153.                             if (d != null && d instanceof TransitionDrawable) {  
  154.                                 ((TransitionDrawable) d).resetTransition();  
  155.                             }  
  156.                         }  
  157.                         postDelayed(new Runnable() {  
  158.                             public void run() {  
  159.                                 child.setPressed(false);  
  160.                                 setPressed(false);  
  161.                                 if (!mDataChanged) {  
  162.                                     post(performClick);  
  163.                                 }  
  164.                                 mTouchMode = TOUCH_MODE_REST;  
  165.                             }  
  166.                         }, ViewConfiguration.getPressedStateDuration());  
  167.                     } else {  
  168.                         mTouchMode = TOUCH_MODE_REST;  
  169.                     }  
  170.                     return true;  
  171.                 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {  
  172.                     post(performClick);  
  173.                 }  
  174.             }  
  175.             mTouchMode = TOUCH_MODE_REST;  
  176.             break;  
  177.         case TOUCH_MODE_SCROLL:  
  178.             final int childCount = getChildCount();  
  179.             if (childCount > 0) {  
  180.                 if (mFirstPosition == 0  
  181.                         && getChildAt(0).getTop() >= mListPadding.top  
  182.                         && mFirstPosition + childCount < mItemCount  
  183.                         && getChildAt(childCount - 1).getBottom() <= getHeight()  
  184.                                 - mListPadding.bottom) {  
  185.                     mTouchMode = TOUCH_MODE_REST;  
  186.                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  187.                 } else {  
  188.                     final VelocityTracker velocityTracker = mVelocityTracker;  
  189.                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  190.                     final int initialVelocity = (int) velocityTracker  
  191.                             .getYVelocity(mActivePointerId);  
  192.                     if (Math.abs(initialVelocity) > mMinimumVelocity) {  
  193.                         if (mFlingRunnable == null) {  
  194.                             mFlingRunnable = new FlingRunnable();  
  195.                         }  
  196.                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);  
  197.                         mFlingRunnable.start(-initialVelocity);  
  198.                     } else {  
  199.                         mTouchMode = TOUCH_MODE_REST;  
  200.                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  201.                     }  
  202.                 }  
  203.             } else {  
  204.                 mTouchMode = TOUCH_MODE_REST;  
  205.                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  206.             }  
  207.             break;  
  208.         }  
  209.         setPressed(false);  
  210.         // Need to redraw since we probably aren't drawing the selector  
  211.         // anymore  
  212.         invalidate();  
  213.         final Handler handler = getHandler();  
  214.         if (handler != null) {  
  215.             handler.removeCallbacks(mPendingCheckForLongPress);  
  216.         }  
  217.         if (mVelocityTracker != null) {  
  218.             mVelocityTracker.recycle();  
  219.             mVelocityTracker = null;  
  220.         }  
  221.         mActivePointerId = INVALID_POINTER;  
  222.         if (PROFILE_SCROLLING) {  
  223.             if (mScrollProfilingStarted) {  
  224.                 Debug.stopMethodTracing();  
  225.                 mScrollProfilingStarted = false;  
  226.             }  
  227.         }  
  228.         break;  
  229.     }  
  230.     case MotionEvent.ACTION_CANCEL: {  
  231.         mTouchMode = TOUCH_MODE_REST;  
  232.         setPressed(false);  
  233.         View motionView = this.getChildAt(mMotionPosition - mFirstPosition);  
  234.         if (motionView != null) {  
  235.             motionView.setPressed(false);  
  236.         }  
  237.         clearScrollingCache();  
  238.         final Handler handler = getHandler();  
  239.         if (handler != null) {  
  240.             handler.removeCallbacks(mPendingCheckForLongPress);  
  241.         }  
  242.         if (mVelocityTracker != null) {  
  243.             mVelocityTracker.recycle();  
  244.             mVelocityTracker = null;  
  245.         }  
  246.         mActivePointerId = INVALID_POINTER;  
  247.         break;  
  248.     }  
  249.     case MotionEvent.ACTION_POINTER_UP: {  
  250.         onSecondaryPointerUp(ev);  
  251.         final int x = mMotionX;  
  252.         final int y = mMotionY;  
  253.         final int motionPosition = pointToPosition(x, y);  
  254.         if (motionPosition >= 0) {  
  255.             // Remember where the motion event started  
  256.             v = getChildAt(motionPosition - mFirstPosition);  
  257.             mMotionViewOriginalTop = v.getTop();  
  258.             mMotionPosition = motionPosition;  
  259.         }  
  260.         mLastY = y;  
  261.         break;  
  262.     }  
  263.     }  
  264.     return true;  
  265. }  

這個方法中的代碼就很是多了,由於它所處理的邏輯也很是多,要監聽各類各樣的觸屏事件。可是咱們目前所關心的就只有手指在屏幕上滑動這一個事件而已,對應的是ACTION_MOVE這個動做,那麼咱們就只看這部分代碼就能夠了。

能夠看到,ACTION_MOVE這個case裏面又嵌套了一個switch語句,是根據當前的TouchMode來選擇的。那這裏我能夠直接告訴你們,當手指在屏幕上滑動時,TouchMode是等於TOUCH_MODE_SCROLL這個值的,至於爲何那又要牽扯到另外的好幾個方法,這裏限於篇幅緣由就再也不展開講解了,喜歡尋根究底的朋友們能夠本身去源碼裏找一找緣由。

這樣的話,代碼就應該會走到第78行的這個case裏面去了,在這個case當中並無什麼太多須要注意的東西,惟一一點很是重要的就是第92行調用的trackMotionScroll()方法,至關於咱們手指只要在屏幕上稍微有一點點移動,這個方法就會被調用,而若是是正常在屏幕上滑動的話,那麼這個方法就會被調用不少次。那麼咱們進入到這個方法中瞧一瞧,代碼以下所示:

[java]  view plain  copy
 
  1. boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {  
  2.     final int childCount = getChildCount();  
  3.     if (childCount == 0) {  
  4.         return true;  
  5.     }  
  6.     final int firstTop = getChildAt(0).getTop();  
  7.     final int lastBottom = getChildAt(childCount - 1).getBottom();  
  8.     final Rect listPadding = mListPadding;  
  9.     final int spaceAbove = listPadding.top - firstTop;  
  10.     final int end = getHeight() - listPadding.bottom;  
  11.     final int spaceBelow = lastBottom - end;  
  12.     final int height = getHeight() - getPaddingBottom() - getPaddingTop();  
  13.     if (deltaY < 0) {  
  14.         deltaY = Math.max(-(height - 1), deltaY);  
  15.     } else {  
  16.         deltaY = Math.min(height - 1, deltaY);  
  17.     }  
  18.     if (incrementalDeltaY < 0) {  
  19.         incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);  
  20.     } else {  
  21.         incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);  
  22.     }  
  23.     final int firstPosition = mFirstPosition;  
  24.     if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {  
  25.         // Don't need to move views down if the top of the first position  
  26.         // is already visible  
  27.         return true;  
  28.     }  
  29.     if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {  
  30.         // Don't need to move views up if the bottom of the last position  
  31.         // is already visible  
  32.         return true;  
  33.     }  
  34.     final boolean down = incrementalDeltaY < 0;  
  35.     final boolean inTouchMode = isInTouchMode();  
  36.     if (inTouchMode) {  
  37.         hideSelector();  
  38.     }  
  39.     final int headerViewsCount = getHeaderViewsCount();  
  40.     final int footerViewsStart = mItemCount - getFooterViewsCount();  
  41.     int start = 0;  
  42.     int count = 0;  
  43.     if (down) {  
  44.         final int top = listPadding.top - incrementalDeltaY;  
  45.         for (int i = 0; i < childCount; i++) {  
  46.             final View child = getChildAt(i);  
  47.             if (child.getBottom() >= top) {  
  48.                 break;  
  49.             } else {  
  50.                 count++;  
  51.                 int position = firstPosition + i;  
  52.                 if (position >= headerViewsCount && position < footerViewsStart) {  
  53.                     mRecycler.addScrapView(child);  
  54.                 }  
  55.             }  
  56.         }  
  57.     } else {  
  58.         final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;  
  59.         for (int i = childCount - 1; i >= 0; i--) {  
  60.             final View child = getChildAt(i);  
  61.             if (child.getTop() <= bottom) {  
  62.                 break;  
  63.             } else {  
  64.                 start = i;  
  65.                 count++;  
  66.                 int position = firstPosition + i;  
  67.                 if (position >= headerViewsCount && position < footerViewsStart) {  
  68.                     mRecycler.addScrapView(child);  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73.     mMotionViewNewTop = mMotionViewOriginalTop + deltaY;  
  74.     mBlockLayoutRequests = true;  
  75.     if (count > 0) {  
  76.         detachViewsFromParent(start, count);  
  77.     }  
  78.     offsetChildrenTopAndBottom(incrementalDeltaY);  
  79.     if (down) {  
  80.         mFirstPosition += count;  
  81.     }  
  82.     invalidate();  
  83.     final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);  
  84.     if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {  
  85.         fillGap(down);  
  86.     }  
  87.     if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {  
  88.         final int childIndex = mSelectedPosition - mFirstPosition;  
  89.         if (childIndex >= 0 && childIndex < getChildCount()) {  
  90.             positionSelector(getChildAt(childIndex));  
  91.         }  
  92.     }  
  93.     mBlockLayoutRequests = false;  
  94.     invokeOnItemScrollListener();  
  95.     awakenScrollBars();  
  96.     return false;  
  97. }  

這個方法接收兩個參數,deltaY表示從手指按下時的位置到當前手指位置的距離,incrementalDeltaY則表示據上次觸發event事件手指在Y方向上位置的改變量,那麼其實咱們就能夠經過incrementalDeltaY的正負值狀況來判斷用戶是向上仍是向下滑動的了。如第34行代碼所示,若是incrementalDeltaY小於0,說明是向下滑動,不然就是向上滑動。

下面將會進行一個邊界值檢測的過程,能夠看到,從第43行開始,當ListView向下滑動的時候,就會進入一個for循環當中,從上往下依次獲取子View,第47行當中,若是該子View的bottom值已經小於top值了,就說明這個子View已經移出屏幕了,因此會調用RecycleBin的addScrapView()方法將這個View加入到廢棄緩存當中,並將count計數器加1,計數器用於記錄有多少個子View被移出了屏幕。那麼若是是ListView向上滑動的話,其實過程是基本相同的,只不過變成了從下往上依次獲取子View,而後判斷該子View的top值是否是大於bottom值了,若是大於的話說明子View已經移出了屏幕,一樣把它加入到廢棄緩存中,並將計數器加1。

接下來在第76行,會根據當前計數器的值來進行一個detach操做,它的做用就是把全部移出屏幕的子View所有detach掉,在ListView的概念當中,全部看不到的View就沒有必要爲它進行保存,由於屏幕外還有成百上千條數據等着顯示呢,一個好的回收策略才能保證ListView的高性能和高效率。緊接着在第78行調用了offsetChildrenTopAndBottom()方法,並將incrementalDeltaY做爲參數傳入,這個方法的做用是讓ListView中全部的子View都按照傳入的參數值進行相應的偏移,這樣就實現了隨着手指的拖動,ListView的內容也會隨着滾動的效果。

而後在第84行會進行判斷,若是ListView中最後一個View的底部已經移入了屏幕,或者ListView中第一個View的頂部移入了屏幕,就會調用fillGap()方法,那麼所以咱們就能夠猜出fillGap()方法是用來加載屏幕外數據的,進入到這個方法中瞧一瞧,以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the gap left open by a touch-scroll. During a touch scroll, 
  3.  * children that remain on screen are shifted and the other ones are 
  4.  * discarded. The role of this method is to fill the gap thus created by 
  5.  * performing a partial layout in the empty space. 
  6.  *  
  7.  * @param down 
  8.  *            true if the scroll is going down, false if it is going up 
  9.  */  
  10. abstract void fillGap(boolean down);  

OK,AbsListView中的fillGap()是一個抽象方法,那麼咱們馬上就可以想到,它的具體實現確定是在ListView中完成的了。回到ListView當中,fillGap()方法的代碼以下所示:

[java]  view plain  copy
 
  1. void fillGap(boolean down) {  
  2.     final int count = getChildCount();  
  3.     if (down) {  
  4.         final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :  
  5.                 getListPaddingTop();  
  6.         fillDown(mFirstPosition + count, startOffset);  
  7.         correctTooHigh(getChildCount());  
  8.     } else {  
  9.         final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :  
  10.                 getHeight() - getListPaddingBottom();  
  11.         fillUp(mFirstPosition - 1, startOffset);  
  12.         correctTooLow(getChildCount());  
  13.     }  
  14. }  

down參數用於表示ListView是向下滑動仍是向上滑動的,能夠看到,若是是向下滑動的話就會調用fillDown()方法,而若是是向上滑動的話就會調用fillUp()方法。那麼這兩個方法咱們都已經很是熟悉了,內部都是經過一個循環來去對ListView進行填充,因此這兩個方法咱們就不看了,可是填充ListView會經過調用makeAndAddView()方法來完成,又是makeAndAddView()方法,但此次的邏輯再次不一樣了,因此咱們仍是回到這個方法瞧一瞧:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

無論怎麼說,這裏首先仍然是會嘗試調用RecycleBin的getActiveView()方法來獲取子佈局,只不過確定是獲取不到的了,由於在第二次Layout過程當中咱們已經從mActiveViews中獲取過了數據,而根據RecycleBin的機制,mActiveViews是不可以重複利用的,所以這裏返回的值確定是null。

既然getActiveView()方法返回的值是null,那麼就仍是會走到第28行的obtainView()方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Get a view and have it show the data associated with the specified 
  3.  * position. This is called when we have already discovered that the view is 
  4.  * not available for reuse in the recycle bin. The only choices left are 
  5.  * converting an old view or making a new one. 
  6.  *  
  7.  * @param position 
  8.  *            The position to display 
  9.  * @param isScrap 
  10.  *            Array of at least 1 boolean, the first entry will become true 
  11.  *            if the returned view was taken from the scrap heap, false if 
  12.  *            otherwise. 
  13.  *  
  14.  * @return A view displaying the data associated with the specified position 
  15.  */  
  16. View obtainView(int position, boolean[] isScrap) {  
  17.     isScrap[0] = false;  
  18.     View scrapView;  
  19.     scrapView = mRecycler.getScrapView(position);  
  20.     View child;  
  21.     if (scrapView != null) {  
  22.         child = mAdapter.getView(position, scrapView, this);  
  23.         if (child != scrapView) {  
  24.             mRecycler.addScrapView(scrapView);  
  25.             if (mCacheColorHint != 0) {  
  26.                 child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  27.             }  
  28.         } else {  
  29.             isScrap[0] = true;  
  30.             dispatchFinishTemporaryDetach(child);  
  31.         }  
  32.     } else {  
  33.         child = mAdapter.getView(position, null, this);  
  34.         if (mCacheColorHint != 0) {  
  35.             child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  36.         }  
  37.     }  
  38.     return child;  
  39. }  

這裏在第19行會調用RecyleBin的getScrapView()方法來嘗試從廢棄緩存中獲取一個View,那麼廢棄緩存有沒有View呢?固然有,由於剛纔在trackMotionScroll()方法中咱們就已經看到了,一旦有任何子View被移出了屏幕,就會將它加入到廢棄緩存中,而從obtainView()方法中的邏輯來看,一旦有新的數據須要顯示到屏幕上,就會嘗試從廢棄緩存中獲取View。因此它們之間就造成了一個生產者和消費者的模式,那麼ListView神奇的地方也就在這裏體現出來了,無論你有任意多條數據須要顯示,ListView中的子View其實來來回回就那麼幾個,移出屏幕的子View會很快被移入屏幕的數據從新利用起來,於是無論咱們加載多少數據都不會出現OOM的狀況,甚至內存都不會有所增長。

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/44996879

Android全部經常使用的原生控件當中,用法最複雜的應該就是ListView了,它專門用於處理那種內容元素不少,手機屏幕沒法展現出全部內容的狀況。ListView可使用列表的形式來展現內容,超出屏幕部分的內容只須要經過手指滑動就能夠移動到屏幕內了。

另外ListView還有一個很是神奇的功能,我相信你們應該都體驗過,即便在ListView中加載很是很是多的數據,好比達到成百上千條甚至更多,ListView都不會發生OOM或者崩潰,並且隨着咱們手指滑動來瀏覽更多數據時,程序所佔用的內存居然都不會跟着增加。那麼ListView是怎麼實現這麼神奇的功能的呢?當初我就抱着學習的心態花了很長時間把ListView的源碼通讀了一遍,基本瞭解了它的工做原理,在感嘆Google大神可以寫出如此精妙代碼的同時我也有所敬畏,由於ListView的代碼量比較大,複雜度也很高,很難用文字表達清楚,因而我就放棄了把它寫成一篇博客的想法。那麼如今回想起來這件事我已經腸子都悔青了,由於沒過幾個月時間我就把當初梳理清晰的源碼又忘的一乾二淨。因而如今我又從新定下心來再次把ListView的源碼重讀了一遍,那麼此次我必定要把它寫成一篇博客,分享給你們的同時也當成我本身的筆記吧。

首先咱們先來看一下ListView的繼承結構,以下圖所示:

能夠看到,ListView的繼承結構仍是至關複雜的,它是直接繼承自的AbsListView,而AbsListView有兩個子實現類,一個是ListView,另外一個就是GridView,所以咱們從這一點就能夠猜出來,ListView和GridView在工做原理和實現上都是有不少共同點的。而後AbsListView又繼承自AdapterView,AdapterView繼承自ViewGroup,後面就是咱們所熟知的了。先把ListView的繼承結構瞭解一下,待會兒有助於咱們更加清晰地分析代碼。

 

Adapter的做用

 

Adapter相信你們都不會陌生,咱們平時使用ListView的時候必定都會用到它。那麼話說回來你們有沒有仔細想過,爲何須要Adapter這個東西呢?總感受正由於有了Adapter,ListView的使用變得要比其它控件複雜得多。那麼這裏咱們就先來學習一下Adapter到底起到了什麼樣的一個做用。

其實說到底,控件就是爲了交互和展現數據用的,只不過ListView更加特殊,它是爲了展現不少不少數據用的,可是ListView只承擔交互和展現工做而已,至於這些數據來自哪裏,ListView是不關心的。所以,咱們能設想到的最基本的ListView工做模式就是要有一個ListView控件和一個數據源。

不過若是真的讓ListView和數據源直接打交道的話,那ListView所要作的適配工做就很是繁雜了。由於數據源這個概念太模糊了,咱們只知道它包含了不少數據而已,至於這個數據源究竟是什麼樣類型,並無嚴格的定義,有多是數組,也有多是集合,甚至有多是數據庫表中查詢出來的遊標。因此說若是ListView真的去爲每一種數據源都進行適配操做的話,一是擴展性會比較差,內置了幾種適配就只有幾種適配,不能動態進行添加。二是超出了它自己應該負責的工做範圍,再也不是僅僅承擔交互和展現工做就能夠了,這樣ListView就會變得比較臃腫。

那麼顯然Android開發團隊是不會容許這種事情發生的,因而就有了Adapter這樣一個機制的出現。顧名思義,Adapter是適配器的意思,它在ListView和數據源之間起到了一個橋樑的做用,ListView並不會直接和數據源打交道,而是會藉助Adapter這個橋樑來去訪問真正的數據源,與以前不一樣的是,Adapter的接口都是統一的,所以ListView不用再去擔憂任何適配方面的問題。而Adapter又是一個接口(interface),它能夠去實現各類各樣的子類,每一個子類都能經過本身的邏輯來去完成特定的功能,以及與特定數據源的適配操做,好比說ArrayAdapter能夠用於數組和List類型的數據源適配,SimpleCursorAdapter能夠用於遊標類型的數據源適配,這樣就很是巧妙地把數據源適配困難的問題解決掉了,而且還擁有至關不錯的擴展性。簡單的原理示意圖以下所示:

固然Adapter的做用不只僅只有數據源適配這一點,還有一個很是很是重要的方法也須要咱們在Adapter當中去重寫,就是getView()方法,這個在下面的文章中還會詳細講到。

RecycleBin機制

那麼在開始分析ListView的源碼以前,還有一個東西是咱們提早須要瞭解的,就是RecycleBin機制,這個機制也是ListView可以實現成百上千條數據都不會OOM最重要的一個緣由。其實RecycleBin的代碼並很少,只有300行左右,它是寫在AbsListView中的一個內部類,因此全部繼承自AbsListView的子類,也就是ListView和GridView,均可以使用這個機制。那咱們來看一下RecycleBin中的主要代碼,以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * The RecycleBin facilitates reuse of views across layouts. The RecycleBin 
  3.  * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are 
  4.  * those views which were onscreen at the start of a layout. By 
  5.  * construction, they are displaying current information. At the end of 
  6.  * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews 
  7.  * are old views that could potentially be used by the adapter to avoid 
  8.  * allocating views unnecessarily. 
  9.  *  
  10.  * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) 
  11.  * @see android.widget.AbsListView.RecyclerListener 
  12.  */  
  13. class RecycleBin {  
  14.     private RecyclerListener mRecyclerListener;  
  15.   
  16.     /** 
  17.      * The position of the first view stored in mActiveViews. 
  18.      */  
  19.     private int mFirstActivePosition;  
  20.   
  21.     /** 
  22.      * Views that were on screen at the start of layout. This array is 
  23.      * populated at the start of layout, and at the end of layout all view 
  24.      * in mActiveViews are moved to mScrapViews. Views in mActiveViews 
  25.      * represent a contiguous range of Views, with position of the first 
  26.      * view store in mFirstActivePosition. 
  27.      */  
  28.     private View[] mActiveViews = new View[0];  
  29.   
  30.     /** 
  31.      * Unsorted views that can be used by the adapter as a convert view. 
  32.      */  
  33.     private ArrayList<View>[] mScrapViews;  
  34.   
  35.     private int mViewTypeCount;  
  36.   
  37.     private ArrayList<View> mCurrentScrap;  
  38.   
  39.     /** 
  40.      * Fill ActiveViews with all of the children of the AbsListView. 
  41.      *  
  42.      * @param childCount 
  43.      *            The minimum number of views mActiveViews should hold 
  44.      * @param firstActivePosition 
  45.      *            The position of the first view that will be stored in 
  46.      *            mActiveViews 
  47.      */  
  48.     void fillActiveViews(int childCount, int firstActivePosition) {  
  49.         if (mActiveViews.length < childCount) {  
  50.             mActiveViews = new View[childCount];  
  51.         }  
  52.         mFirstActivePosition = firstActivePosition;  
  53.         final View[] activeViews = mActiveViews;  
  54.         for (int i = 0; i < childCount; i++) {  
  55.             View child = getChildAt(i);  
  56.             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();  
  57.             // Don't put header or footer views into the scrap heap  
  58.             if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  59.                 // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in  
  60.                 // active views.  
  61.                 // However, we will NOT place them into scrap views.  
  62.                 activeViews[i] = child;  
  63.             }  
  64.         }  
  65.     }  
  66.   
  67.     /** 
  68.      * Get the view corresponding to the specified position. The view will 
  69.      * be removed from mActiveViews if it is found. 
  70.      *  
  71.      * @param position 
  72.      *            The position to look up in mActiveViews 
  73.      * @return The view if it is found, null otherwise 
  74.      */  
  75.     View getActiveView(int position) {  
  76.         int index = position - mFirstActivePosition;  
  77.         final View[] activeViews = mActiveViews;  
  78.         if (index >= 0 && index < activeViews.length) {  
  79.             final View match = activeViews[index];  
  80.             activeViews[index] = null;  
  81.             return match;  
  82.         }  
  83.         return null;  
  84.     }  
  85.   
  86.     /** 
  87.      * Put a view into the ScapViews list. These views are unordered. 
  88.      *  
  89.      * @param scrap 
  90.      *            The view to add 
  91.      */  
  92.     void addScrapView(View scrap) {  
  93.         AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();  
  94.         if (lp == null) {  
  95.             return;  
  96.         }  
  97.         // Don't put header or footer views or views that should be ignored  
  98.         // into the scrap heap  
  99.         int viewType = lp.viewType;  
  100.         if (!shouldRecycleViewType(viewType)) {  
  101.             if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  102.                 removeDetachedView(scrap, false);  
  103.             }  
  104.             return;  
  105.         }  
  106.         if (mViewTypeCount == 1) {  
  107.             dispatchFinishTemporaryDetach(scrap);  
  108.             mCurrentScrap.add(scrap);  
  109.         } else {  
  110.             dispatchFinishTemporaryDetach(scrap);  
  111.             mScrapViews[viewType].add(scrap);  
  112.         }  
  113.   
  114.         if (mRecyclerListener != null) {  
  115.             mRecyclerListener.onMovedToScrapHeap(scrap);  
  116.         }  
  117.     }  
  118.   
  119.     /** 
  120.      * @return A view from the ScrapViews collection. These are unordered. 
  121.      */  
  122.     View getScrapView(int position) {  
  123.         ArrayList<View> scrapViews;  
  124.         if (mViewTypeCount == 1) {  
  125.             scrapViews = mCurrentScrap;  
  126.             int size = scrapViews.size();  
  127.             if (size > 0) {  
  128.                 return scrapViews.remove(size - 1);  
  129.             } else {  
  130.                 return null;  
  131.             }  
  132.         } else {  
  133.             int whichScrap = mAdapter.getItemViewType(position);  
  134.             if (whichScrap >= 0 && whichScrap < mScrapViews.length) {  
  135.                 scrapViews = mScrapViews[whichScrap];  
  136.                 int size = scrapViews.size();  
  137.                 if (size > 0) {  
  138.                     return scrapViews.remove(size - 1);  
  139.                 }  
  140.             }  
  141.         }  
  142.         return null;  
  143.     }  
  144.   
  145.     public void setViewTypeCount(int viewTypeCount) {  
  146.         if (viewTypeCount < 1) {  
  147.             throw new IllegalArgumentException("Can't have a viewTypeCount < 1");  
  148.         }  
  149.         // noinspection unchecked  
  150.         ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];  
  151.         for (int i = 0; i < viewTypeCount; i++) {  
  152.             scrapViews[i] = new ArrayList<View>();  
  153.         }  
  154.         mViewTypeCount = viewTypeCount;  
  155.         mCurrentScrap = scrapViews[0];  
  156.         mScrapViews = scrapViews;  
  157.     }  
  158.   
  159. }  

 

這裏的RecycleBin代碼並不全,我只是把最主要的幾個方法提了出來。那麼咱們先來對這幾個方法進行簡單解讀,這對後面分析ListView的工做原理將會有很大的幫助。

  • fillActiveViews() 這個方法接收兩個參數,第一個參數表示要存儲的view的數量,第二個參數表示ListView中第一個可見元素的position值。RecycleBin當中使用mActiveViews這個數組來存儲View,調用這個方法後就會根據傳入的參數來將ListView中的指定元素存儲到mActiveViews數組當中。
  • getActiveView() 這個方法和fillActiveViews()是對應的,用於從mActiveViews數組當中獲取數據。該方法接收一個position參數,表示元素在ListView當中的位置,方法內部會自動將position值轉換成mActiveViews數組對應的下標值。須要注意的是,mActiveViews當中所存儲的View,一旦被獲取了以後就會從mActiveViews當中移除,下次獲取一樣位置的View將會返回null,也就是說mActiveViews不能被重複利用。
  • addScrapView() 用於將一個廢棄的View進行緩存,該方法接收一個View參數,當有某個View肯定要廢棄掉的時候(好比滾動出了屏幕),就應該調用這個方法來對View進行緩存,RecycleBin當中使用mScrapViews和mCurrentScrap這兩個List來存儲廢棄View。
  • getScrapView 用於從廢棄緩存中取出一個View,這些廢棄緩存中的View是沒有順序可言的,所以getScrapView()方法中的算法也很是簡單,就是直接從mCurrentScrap當中獲取尾部的一個scrap view進行返回。
  • setViewTypeCount() 咱們都知道Adapter當中能夠重寫一個getViewTypeCount()來表示ListView中有幾種類型的數據項,而setViewTypeCount()方法的做用就是爲每種類型的數據項都單獨啓用一個RecycleBin緩存機制。實際上,getViewTypeCount()方法一般狀況下使用的並非不少,因此咱們只要知道RecycleBin當中有這樣一個功能就好了。

瞭解了RecycleBin中的主要方法以及它們的用處以後,下面就能夠開始來分析ListView的工做原理了,這裏我將仍是按照之前分析源碼的方式來進行,即跟着主線執行流程來逐步閱讀並點到即止,否則的話要是把ListView全部的代碼都貼出來,那麼本篇文章將會很長很長了。

第一次Layout

無論怎麼說,ListView即便再特殊最終仍是繼承自View的,所以它的執行流程還將會按照View的規則來執行,對於這方面不太熟悉的朋友能夠參考我以前寫的 Android視圖繪製流程徹底解析,帶你一步步深刻了解View(二) 。

View的執行流程無非就分爲三步,onMeasure()用於測量View的大小,onLayout()用於肯定View的佈局,onDraw()用於將View繪製到界面上。而在ListView當中,onMeasure()並無什麼特殊的地方,由於它終歸是一個View,佔用的空間最多而且一般也就是整個屏幕。onDraw()在ListView當中也沒有什麼意義,由於ListView自己並不負責繪製,而是由ListView當中的子元素來進行繪製的。那麼ListView大部分的神奇功能其實都是在onLayout()方法中進行的了,所以咱們本篇文章也是主要分析的這個方法裏的內容。

若是你到ListView源碼中去找一找,你會發現ListView中是沒有onLayout()這個方法的,這是由於這個方法是在ListView的父類AbsListView中實現的,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Subclasses should NOT override this method but {@link #layoutChildren()} 
  3.  * instead. 
  4.  */  
  5. @Override  
  6. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  7.     super.onLayout(changed, l, t, r, b);  
  8.     mInLayout = true;  
  9.     if (changed) {  
  10.         int childCount = getChildCount();  
  11.         for (int i = 0; i < childCount; i++) {  
  12.             getChildAt(i).forceLayout();  
  13.         }  
  14.         mRecycler.markChildrenDirty();  
  15.     }  
  16.     layoutChildren();  
  17.     mInLayout = false;  
  18. }  

能夠看到,onLayout()方法中並無作什麼複雜的邏輯操做,主要就是一個判斷,若是ListView的大小或者位置發生了變化,那麼changed變量就會變成true,此時會要求全部的子佈局都強制進行重繪。除此以外倒沒有什麼難理解的地方了,不過咱們注意到,在第16行調用了layoutChildren()這個方法,從方法名上咱們就能夠猜出這個方法是用來進行子元素佈局的,不過進入到這個方法當中你會發現這是個空方法,沒有一行代碼。這固然是能夠理解的了,由於子元素的佈局應該是由具體的實現類來負責完成的,而不是由父類完成。那麼進入ListView的layoutChildren()方法,代碼以下所示:

[java]  view plain  copy
 
  1. @Override  
  2. protected void layoutChildren() {  
  3.     final boolean blockLayoutRequests = mBlockLayoutRequests;  
  4.     if (!blockLayoutRequests) {  
  5.         mBlockLayoutRequests = true;  
  6.     } else {  
  7.         return;  
  8.     }  
  9.     try {  
  10.         super.layoutChildren();  
  11.         invalidate();  
  12.         if (mAdapter == null) {  
  13.             resetList();  
  14.             invokeOnItemScrollListener();  
  15.             return;  
  16.         }  
  17.         int childrenTop = mListPadding.top;  
  18.         int childrenBottom = getBottom() - getTop() - mListPadding.bottom;  
  19.         int childCount = getChildCount();  
  20.         int index = 0;  
  21.         int delta = 0;  
  22.         View sel;  
  23.         View oldSel = null;  
  24.         View oldFirst = null;  
  25.         View newSel = null;  
  26.         View focusLayoutRestoreView = null;  
  27.         // Remember stuff we will need down below  
  28.         switch (mLayoutMode) {  
  29.         case LAYOUT_SET_SELECTION:  
  30.             index = mNextSelectedPosition - mFirstPosition;  
  31.             if (index >= 0 && index < childCount) {  
  32.                 newSel = getChildAt(index);  
  33.             }  
  34.             break;  
  35.         case LAYOUT_FORCE_TOP:  
  36.         case LAYOUT_FORCE_BOTTOM:  
  37.         case LAYOUT_SPECIFIC:  
  38.         case LAYOUT_SYNC:  
  39.             break;  
  40.         case LAYOUT_MOVE_SELECTION:  
  41.         default:  
  42.             // Remember the previously selected view  
  43.             index = mSelectedPosition - mFirstPosition;  
  44.             if (index >= 0 && index < childCount) {  
  45.                 oldSel = getChildAt(index);  
  46.             }  
  47.             // Remember the previous first child  
  48.             oldFirst = getChildAt(0);  
  49.             if (mNextSelectedPosition >= 0) {  
  50.                 delta = mNextSelectedPosition - mSelectedPosition;  
  51.             }  
  52.             // Caution: newSel might be null  
  53.             newSel = getChildAt(index + delta);  
  54.         }  
  55.         boolean dataChanged = mDataChanged;  
  56.         if (dataChanged) {  
  57.             handleDataChanged();  
  58.         }  
  59.         // Handle the empty set by removing all views that are visible  
  60.         // and calling it a day  
  61.         if (mItemCount == 0) {  
  62.             resetList();  
  63.             invokeOnItemScrollListener();  
  64.             return;  
  65.         } else if (mItemCount != mAdapter.getCount()) {  
  66.             throw new IllegalStateException("The content of the adapter has changed but "  
  67.                     + "ListView did not receive a notification. Make sure the content of "  
  68.                     + "your adapter is not modified from a background thread, but only "  
  69.                     + "from the UI thread. [in ListView(" + getId() + ", " + getClass()   
  70.                     + ") with Adapter(" + mAdapter.getClass() + ")]");  
  71.         }  
  72.         setSelectedPositionInt(mNextSelectedPosition);  
  73.         // Pull all children into the RecycleBin.  
  74.         // These views will be reused if possible  
  75.         final int firstPosition = mFirstPosition;  
  76.         final RecycleBin recycleBin = mRecycler;  
  77.         // reset the focus restoration  
  78.         View focusLayoutRestoreDirectChild = null;  
  79.         // Don't put header or footer views into the Recycler. Those are  
  80.         // already cached in mHeaderViews;  
  81.         if (dataChanged) {  
  82.             for (int i = 0; i < childCount; i++) {  
  83.                 recycleBin.addScrapView(getChildAt(i));  
  84.                 if (ViewDebug.TRACE_RECYCLER) {  
  85.                     ViewDebug.trace(getChildAt(i),  
  86.                             ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);  
  87.                 }  
  88.             }  
  89.         } else {  
  90.             recycleBin.fillActiveViews(childCount, firstPosition);  
  91.         }  
  92.         // take focus back to us temporarily to avoid the eventual  
  93.         // call to clear focus when removing the focused child below  
  94.         // from messing things up when ViewRoot assigns focus back  
  95.         // to someone else  
  96.         final View focusedChild = getFocusedChild();  
  97.         if (focusedChild != null) {  
  98.             // TODO: in some cases focusedChild.getParent() == null  
  99.             // we can remember the focused view to restore after relayout if the  
  100.             // data hasn't changed, or if the focused position is a header or footer  
  101.             if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {  
  102.                 focusLayoutRestoreDirectChild = focusedChild;  
  103.                 // remember the specific view that had focus  
  104.                 focusLayoutRestoreView = findFocus();  
  105.                 if (focusLayoutRestoreView != null) {  
  106.                     // tell it we are going to mess with it  
  107.                     focusLayoutRestoreView.onStartTemporaryDetach();  
  108.                 }  
  109.             }  
  110.             requestFocus();  
  111.         }  
  112.         // Clear out old views  
  113.         detachAllViewsFromParent();  
  114.         switch (mLayoutMode) {  
  115.         case LAYOUT_SET_SELECTION:  
  116.             if (newSel != null) {  
  117.                 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);  
  118.             } else {  
  119.                 sel = fillFromMiddle(childrenTop, childrenBottom);  
  120.             }  
  121.             break;  
  122.         case LAYOUT_SYNC:  
  123.             sel = fillSpecific(mSyncPosition, mSpecificTop);  
  124.             break;  
  125.         case LAYOUT_FORCE_BOTTOM:  
  126.             sel = fillUp(mItemCount - 1, childrenBottom);  
  127.             adjustViewsUpOrDown();  
  128.             break;  
  129.         case LAYOUT_FORCE_TOP:  
  130.             mFirstPosition = 0;  
  131.             sel = fillFromTop(childrenTop);  
  132.             adjustViewsUpOrDown();  
  133.             break;  
  134.         case LAYOUT_SPECIFIC:  
  135.             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);  
  136.             break;  
  137.         case LAYOUT_MOVE_SELECTION:  
  138.             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);  
  139.             break;  
  140.         default:  
  141.             if (childCount == 0) {  
  142.                 if (!mStackFromBottom) {  
  143.                     final int position = lookForSelectablePosition(0, true);  
  144.                     setSelectedPositionInt(position);  
  145.                     sel = fillFromTop(childrenTop);  
  146.                 } else {  
  147.                     final int position = lookForSelectablePosition(mItemCount - 1, false);  
  148.                     setSelectedPositionInt(position);  
  149.                     sel = fillUp(mItemCount - 1, childrenBottom);  
  150.                 }  
  151.             } else {  
  152.                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {  
  153.                     sel = fillSpecific(mSelectedPosition,  
  154.                             oldSel == null ? childrenTop : oldSel.getTop());  
  155.                 } else if (mFirstPosition < mItemCount) {  
  156.                     sel = fillSpecific(mFirstPosition,  
  157.                             oldFirst == null ? childrenTop : oldFirst.getTop());  
  158.                 } else {  
  159.                     sel = fillSpecific(0, childrenTop);  
  160.                 }  
  161.             }  
  162.             break;  
  163.         }  
  164.         // Flush any cached views that did not get reused above  
  165.         recycleBin.scrapActiveViews();  
  166.         if (sel != null) {  
  167.             // the current selected item should get focus if items  
  168.             // are focusable  
  169.             if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {  
  170.                 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&  
  171.                         focusLayoutRestoreView.requestFocus()) || sel.requestFocus();  
  172.                 if (!focusWasTaken) {  
  173.                     // selected item didn't take focus, fine, but still want  
  174.                     // to make sure something else outside of the selected view  
  175.                     // has focus  
  176.                     final View focused = getFocusedChild();  
  177.                     if (focused != null) {  
  178.                         focused.clearFocus();  
  179.                     }  
  180.                     positionSelector(sel);  
  181.                 } else {  
  182.                     sel.setSelected(false);  
  183.                     mSelectorRect.setEmpty();  
  184.                 }  
  185.             } else {  
  186.                 positionSelector(sel);  
  187.             }  
  188.             mSelectedTop = sel.getTop();  
  189.         } else {  
  190.             if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {  
  191.                 View child = getChildAt(mMotionPosition - mFirstPosition);  
  192.                 if (child != null) positionSelector(child);  
  193.             } else {  
  194.                 mSelectedTop = 0;  
  195.                 mSelectorRect.setEmpty();  
  196.             }  
  197.             // even if there is not selected position, we may need to restore  
  198.             // focus (i.e. something focusable in touch mode)  
  199.             if (hasFocus() && focusLayoutRestoreView != null) {  
  200.                 focusLayoutRestoreView.requestFocus();  
  201.             }  
  202.         }  
  203.         // tell focus view we are done mucking with it, if it is still in  
  204.         // our view hierarchy.  
  205.         if (focusLayoutRestoreView != null  
  206.                 && focusLayoutRestoreView.getWindowToken() != null) {  
  207.             focusLayoutRestoreView.onFinishTemporaryDetach();  
  208.         }  
  209.         mLayoutMode = LAYOUT_NORMAL;  
  210.         mDataChanged = false;  
  211.         mNeedSync = false;  
  212.         setNextSelectedPositionInt(mSelectedPosition);  
  213.         updateScrollIndicators();  
  214.         if (mItemCount > 0) {  
  215.             checkSelectionChanged();  
  216.         }  
  217.         invokeOnItemScrollListener();  
  218.     } finally {  
  219.         if (!blockLayoutRequests) {  
  220.             mBlockLayoutRequests = false;  
  221.         }  
  222.     }  
  223. }  

這段代碼比較長,咱們挑重點的看。首先能夠肯定的是,ListView當中目前尚未任何子View,數據都仍是由Adapter管理的,並無展現到界面上,所以第19行getChildCount()方法獲得的值確定是0。接着在第81行會根據dataChanged這個布爾型的值來判斷執行邏輯,dataChanged只有在數據源發生改變的狀況下才會變成true,其它狀況都是false,所以這裏會進入到第90行的執行邏輯,調用RecycleBin的fillActiveViews()方法。按理來講,調用fillActiveViews()方法是爲了將ListView的子View進行緩存的,但是目前ListView中尚未任何的子View,所以這一行暫時還起不了任何做用。

接下來在第114行會根據mLayoutMode的值來決定佈局模式,默認狀況下都是普通模式LAYOUT_NORMAL,所以會進入到第140行的default語句當中。而下面又會緊接着進行兩次if判斷,childCount目前是等於0的,而且默認的佈局順序是從上往下,所以會進入到第145行的fillFromTop()方法,咱們跟進去瞧一瞧:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the list from top to bottom, starting with mFirstPosition 
  3.  * 
  4.  * @param nextTop The location where the top of the first item should be 
  5.  *        drawn 
  6.  * 
  7.  * @return The view that is currently selected 
  8.  */  
  9. private View fillFromTop(int nextTop) {  
  10.     mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);  
  11.     mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);  
  12.     if (mFirstPosition < 0) {  
  13.         mFirstPosition = 0;  
  14.     }  
  15.     return fillDown(mFirstPosition, nextTop);  
  16. }  

從這個方法的註釋中能夠看出,它所負責的主要任務就是從mFirstPosition開始,自頂至底去填充ListView。而這個方法自己並無什麼邏輯,就是判斷了一下mFirstPosition值的合法性,而後調用fillDown()方法,那麼咱們就有理由能夠猜想,填充ListView的操做是在fillDown()方法中完成的。進入fillDown()方法,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the list from pos down to the end of the list view. 
  3.  * 
  4.  * @param pos The first position to put in the list 
  5.  * 
  6.  * @param nextTop The location where the top of the item associated with pos 
  7.  *        should be drawn 
  8.  * 
  9.  * @return The view that is currently selected, if it happens to be in the 
  10.  *         range that we draw. 
  11.  */  
  12. private View fillDown(int pos, int nextTop) {  
  13.     View selectedView = null;  
  14.     int end = (getBottom() - getTop()) - mListPadding.bottom;  
  15.     while (nextTop < end && pos < mItemCount) {  
  16.         // is this the selected item?  
  17.         boolean selected = pos == mSelectedPosition;  
  18.         View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);  
  19.         nextTop = child.getBottom() + mDividerHeight;  
  20.         if (selected) {  
  21.             selectedView = child;  
  22.         }  
  23.         pos++;  
  24.     }  
  25.     return selectedView;  
  26. }  

能夠看到,這裏使用了一個while循環來執行重複邏輯,一開始nextTop的值是第一個子元素頂部距離整個ListView頂部的像素值,pos則是剛剛傳入的mFirstPosition的值,而end是ListView底部減去頂部所得的像素值,mItemCount則是Adapter中的元素數量。所以一開始的狀況下nextTop一定是小於end值的,而且pos也是小於mItemCount值的。那麼每執行一次while循環,pos的值都會加1,而且nextTop也會增長,當nextTop大於等於end時,也就是子元素已經超出當前屏幕了,或者pos大於等於mItemCount時,也就是全部Adapter中的元素都被遍歷結束了,就會跳出while循環。

那麼while循環當中又作了什麼事情呢?值得讓人留意的就是第18行調用的makeAndAddView()方法,進入到這個方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

這裏在第19行嘗試從RecycleBin當中快速獲取一個active view,不過很遺憾的是目前RecycleBin當中尚未緩存任何的View,因此這裏獲得的值確定是null。那麼取得了null以後就會繼續向下運行,到第28行會調用obtainView()方法來再次嘗試獲取一個View,此次的obtainView()方法是能夠保證必定返回一個View的,因而下面馬上將獲取到的View傳入到了setupChild()方法當中。那麼obtainView()內部究竟是怎麼工做的呢?咱們先進入到這個方法裏面看一下:

[java]  view plain  copy
 
  1. /** 
  2.  * Get a view and have it show the data associated with the specified 
  3.  * position. This is called when we have already discovered that the view is 
  4.  * not available for reuse in the recycle bin. The only choices left are 
  5.  * converting an old view or making a new one. 
  6.  *  
  7.  * @param position 
  8.  *            The position to display 
  9.  * @param isScrap 
  10.  *            Array of at least 1 boolean, the first entry will become true 
  11.  *            if the returned view was taken from the scrap heap, false if 
  12.  *            otherwise. 
  13.  *  
  14.  * @return A view displaying the data associated with the specified position 
  15.  */  
  16. View obtainView(int position, boolean[] isScrap) {  
  17.     isScrap[0] = false;  
  18.     View scrapView;  
  19.     scrapView = mRecycler.getScrapView(position);  
  20.     View child;  
  21.     if (scrapView != null) {  
  22.         child = mAdapter.getView(position, scrapView, this);  
  23.         if (child != scrapView) {  
  24.             mRecycler.addScrapView(scrapView);  
  25.             if (mCacheColorHint != 0) {  
  26.                 child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  27.             }  
  28.         } else {  
  29.             isScrap[0] = true;  
  30.             dispatchFinishTemporaryDetach(child);  
  31.         }  
  32.     } else {  
  33.         child = mAdapter.getView(position, null, this);  
  34.         if (mCacheColorHint != 0) {  
  35.             child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  36.         }  
  37.     }  
  38.     return child;  
  39. }  

obtainView()方法中的代碼並很少,但卻包含了很是很是重要的邏輯,不誇張的說,整個ListView中最重要的內容可能就在這個方法裏了。那麼咱們仍是按照執行流程來看,在第19行代碼中調用了RecycleBin的getScrapView()方法來嘗試獲取一個廢棄緩存中的View,一樣的道理,這裏確定是獲取不到的,getScrapView()方法會返回一個null。這時該怎麼辦呢?沒有關係,代碼會執行到第33行,調用mAdapter的getView()方法來去獲取一個View。那麼mAdapter是什麼呢?固然就是當前ListView關聯的適配器了。而getView()方法又是什麼呢?還用說嗎,這個就是咱們平時使用ListView時最最常常重寫的一個方法了,這裏getView()方法中傳入了三個參數,分別是position,null和this。

 

那麼咱們平時寫ListView的Adapter時,getView()方法一般會怎麼寫呢?這裏我舉個簡單的例子:

[java]  view plain  copy
 
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     Fruit fruit = getItem(position);  
  4.     View view;  
  5.     if (convertView == null) {  
  6.         view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  7.     } else {  
  8.         view = convertView;  
  9.     }  
  10.     ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  11.     TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  12.     fruitImage.setImageResource(fruit.getImageId());  
  13.     fruitName.setText(fruit.getName());  
  14.     return view;  
  15. }  

getView()方法接受的三個參數,第一個參數position表明當前子元素的的位置,咱們能夠經過具體的位置來獲取與其相關的數據。第二個參數convertView,剛纔傳入的是null,說明沒有convertView能夠利用,所以咱們會調用LayoutInflater的inflate()方法來去加載一個佈局。接下來會對這個view進行一些屬性和值的設定,最後將view返回。

 

那麼這個View也會做爲obtainView()的結果進行返回,並最終傳入到setupChild()方法當中。其實也就是說,第一次layout過程中,全部的子View都是調用LayoutInflater的inflate()方法加載出來的,這樣就會相對比較耗時,可是不用擔憂,後面就不會再有這種狀況了,那麼咱們繼續往下看:

[java]  view plain  copy
 
  1. /** 
  2.  * Add a view as a child and make sure it is measured (if necessary) and 
  3.  * positioned properly. 
  4.  * 
  5.  * @param child The view to add 
  6.  * @param position The position of this child 
  7.  * @param y The y position relative to which this view will be positioned 
  8.  * @param flowDown If true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @param recycled Has this view been pulled from the recycle bin? If so it 
  13.  *        does not need to be remeasured. 
  14.  */  
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,  
  16.         boolean selected, boolean recycled) {  
  17.     final boolean isSelected = selected && shouldShowSelector();  
  18.     final boolean updateChildSelected = isSelected != child.isSelected();  
  19.     final int mode = mTouchMode;  
  20.     final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&  
  21.             mMotionPosition == position;  
  22.     final boolean updateChildPressed = isPressed != child.isPressed();  
  23.     final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  
  24.     // Respect layout params that are already in the view. Otherwise make some up...  
  25.     // noinspection unchecked  
  26.     AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();  
  27.     if (p == null) {  
  28.         p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  29.                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);  
  30.     }  
  31.     p.viewType = mAdapter.getItemViewType(position);  
  32.     if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&  
  33.             p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {  
  34.         attachViewToParent(child, flowDown ? -1 : 0, p);  
  35.     } else {  
  36.         p.forceAdd = false;  
  37.         if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  38.             p.recycledHeaderFooter = true;  
  39.         }  
  40.         addViewInLayout(child, flowDown ? -1 : 0, p, true);  
  41.     }  
  42.     if (updateChildSelected) {  
  43.         child.setSelected(isSelected);  
  44.     }  
  45.     if (updateChildPressed) {  
  46.         child.setPressed(isPressed);  
  47.     }  
  48.     if (needToMeasure) {  
  49.         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
  50.                 mListPadding.left + mListPadding.right, p.width);  
  51.         int lpHeight = p.height;  
  52.         int childHeightSpec;  
  53.         if (lpHeight > 0) {  
  54.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  55.         } else {  
  56.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  57.         }  
  58.         child.measure(childWidthSpec, childHeightSpec);  
  59.     } else {  
  60.         cleanupLayoutState(child);  
  61.     }  
  62.     final int w = child.getMeasuredWidth();  
  63.     final int h = child.getMeasuredHeight();  
  64.     final int childTop = flowDown ? y : y - h;  
  65.     if (needToMeasure) {  
  66.         final int childRight = childrenLeft + w;  
  67.         final int childBottom = childTop + h;  
  68.         child.layout(childrenLeft, childTop, childRight, childBottom);  
  69.     } else {  
  70.         child.offsetLeftAndRight(childrenLeft - child.getLeft());  
  71.         child.offsetTopAndBottom(childTop - child.getTop());  
  72.     }  
  73.     if (mCachingStarted && !child.isDrawingCacheEnabled()) {  
  74.         child.setDrawingCacheEnabled(true);  
  75.     }  
  76. }  

setupChild()方法當中的代碼雖然比較多,可是咱們只看核心代碼的話就很是簡單了,剛纔調用obtainView()方法獲取到的子元素View,這裏在第40行調用了addViewInLayout()方法將它添加到了ListView當中。那麼根據fillDown()方法中的while循環,會讓子元素View將整個ListView控件填滿而後就跳出,也就是說即便咱們的Adapter中有一千條數據,ListView也只會加載第一屏的數據,剩下的數據反正目前在屏幕上也看不到,因此不會去作多餘的加載工做,這樣就能夠保證ListView中的內容可以迅速展現到屏幕上。

 

那麼到此爲止,第一次Layout過程結束。

第二次Layout

 

雖然我在源碼中並無找出具體的緣由,但若是你本身作一下實驗的話就會發現,即便是一個再簡單的View,在展現到界面上以前都會經歷至少兩次onMeasure()和兩次onLayout()的過程。其實這只是一個很小的細節,平時對咱們影響並不大,由於無論是onMeasure()或者onLayout()幾回,反正都是執行的相同的邏輯,咱們並不須要進行過多關心。可是在ListView中狀況就不同了,由於這就意味着layoutChildren()過程會執行兩次,而這個過程中涉及到向ListView中添加子元素,若是相同的邏輯執行兩遍的話,那麼ListView中就會存在一份重複的數據了。所以ListView在layoutChildren()過程中作了第二次Layout的邏輯處理,很是巧妙地解決了這個問題,下面咱們就來分析一下第二次Layout的過程。

其實第二次Layout和第一次Layout的基本流程是差很少的,那麼咱們仍是從layoutChildren()方法開始看起:

[java]  view plain  copy
 
  1. @Override  
  2. protected void layoutChildren() {  
  3.     final boolean blockLayoutRequests = mBlockLayoutRequests;  
  4.     if (!blockLayoutRequests) {  
  5.         mBlockLayoutRequests = true;  
  6.     } else {  
  7.         return;  
  8.     }  
  9.     try {  
  10.         super.layoutChildren();  
  11.         invalidate();  
  12.         if (mAdapter == null) {  
  13.             resetList();  
  14.             invokeOnItemScrollListener();  
  15.             return;  
  16.         }  
  17.         int childrenTop = mListPadding.top;  
  18.         int childrenBottom = getBottom() - getTop() - mListPadding.bottom;  
  19.         int childCount = getChildCount();  
  20.         int index = 0;  
  21.         int delta = 0;  
  22.         View sel;  
  23.         View oldSel = null;  
  24.         View oldFirst = null;  
  25.         View newSel = null;  
  26.         View focusLayoutRestoreView = null;  
  27.         // Remember stuff we will need down below  
  28.         switch (mLayoutMode) {  
  29.         case LAYOUT_SET_SELECTION:  
  30.             index = mNextSelectedPosition - mFirstPosition;  
  31.             if (index >= 0 && index < childCount) {  
  32.                 newSel = getChildAt(index);  
  33.             }  
  34.             break;  
  35.         case LAYOUT_FORCE_TOP:  
  36.         case LAYOUT_FORCE_BOTTOM:  
  37.         case LAYOUT_SPECIFIC:  
  38.         case LAYOUT_SYNC:  
  39.             break;  
  40.         case LAYOUT_MOVE_SELECTION:  
  41.         default:  
  42.             // Remember the previously selected view  
  43.             index = mSelectedPosition - mFirstPosition;  
  44.             if (index >= 0 && index < childCount) {  
  45.                 oldSel = getChildAt(index);  
  46.             }  
  47.             // Remember the previous first child  
  48.             oldFirst = getChildAt(0);  
  49.             if (mNextSelectedPosition >= 0) {  
  50.                 delta = mNextSelectedPosition - mSelectedPosition;  
  51.             }  
  52.             // Caution: newSel might be null  
  53.             newSel = getChildAt(index + delta);  
  54.         }  
  55.         boolean dataChanged = mDataChanged;  
  56.         if (dataChanged) {  
  57.             handleDataChanged();  
  58.         }  
  59.         // Handle the empty set by removing all views that are visible  
  60.         // and calling it a day  
  61.         if (mItemCount == 0) {  
  62.             resetList();  
  63.             invokeOnItemScrollListener();  
  64.             return;  
  65.         } else if (mItemCount != mAdapter.getCount()) {  
  66.             throw new IllegalStateException("The content of the adapter has changed but "  
  67.                     + "ListView did not receive a notification. Make sure the content of "  
  68.                     + "your adapter is not modified from a background thread, but only "  
  69.                     + "from the UI thread. [in ListView(" + getId() + ", " + getClass()   
  70.                     + ") with Adapter(" + mAdapter.getClass() + ")]");  
  71.         }  
  72.         setSelectedPositionInt(mNextSelectedPosition);  
  73.         // Pull all children into the RecycleBin.  
  74.         // These views will be reused if possible  
  75.         final int firstPosition = mFirstPosition;  
  76.         final RecycleBin recycleBin = mRecycler;  
  77.         // reset the focus restoration  
  78.         View focusLayoutRestoreDirectChild = null;  
  79.         // Don't put header or footer views into the Recycler. Those are  
  80.         // already cached in mHeaderViews;  
  81.         if (dataChanged) {  
  82.             for (int i = 0; i < childCount; i++) {  
  83.                 recycleBin.addScrapView(getChildAt(i));  
  84.                 if (ViewDebug.TRACE_RECYCLER) {  
  85.                     ViewDebug.trace(getChildAt(i),  
  86.                             ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);  
  87.                 }  
  88.             }  
  89.         } else {  
  90.             recycleBin.fillActiveViews(childCount, firstPosition);  
  91.         }  
  92.         // take focus back to us temporarily to avoid the eventual  
  93.         // call to clear focus when removing the focused child below  
  94.         // from messing things up when ViewRoot assigns focus back  
  95.         // to someone else  
  96.         final View focusedChild = getFocusedChild();  
  97.         if (focusedChild != null) {  
  98.             // TODO: in some cases focusedChild.getParent() == null  
  99.             // we can remember the focused view to restore after relayout if the  
  100.             // data hasn't changed, or if the focused position is a header or footer  
  101.             if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {  
  102.                 focusLayoutRestoreDirectChild = focusedChild;  
  103.                 // remember the specific view that had focus  
  104.                 focusLayoutRestoreView = findFocus();  
  105.                 if (focusLayoutRestoreView != null) {  
  106.                     // tell it we are going to mess with it  
  107.                     focusLayoutRestoreView.onStartTemporaryDetach();  
  108.                 }  
  109.             }  
  110.             requestFocus();  
  111.         }  
  112.         // Clear out old views  
  113.         detachAllViewsFromParent();  
  114.         switch (mLayoutMode) {  
  115.         case LAYOUT_SET_SELECTION:  
  116.             if (newSel != null) {  
  117.                 sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);  
  118.             } else {  
  119.                 sel = fillFromMiddle(childrenTop, childrenBottom);  
  120.             }  
  121.             break;  
  122.         case LAYOUT_SYNC:  
  123.             sel = fillSpecific(mSyncPosition, mSpecificTop);  
  124.             break;  
  125.         case LAYOUT_FORCE_BOTTOM:  
  126.             sel = fillUp(mItemCount - 1, childrenBottom);  
  127.             adjustViewsUpOrDown();  
  128.             break;  
  129.         case LAYOUT_FORCE_TOP:  
  130.             mFirstPosition = 0;  
  131.             sel = fillFromTop(childrenTop);  
  132.             adjustViewsUpOrDown();  
  133.             break;  
  134.         case LAYOUT_SPECIFIC:  
  135.             sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);  
  136.             break;  
  137.         case LAYOUT_MOVE_SELECTION:  
  138.             sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);  
  139.             break;  
  140.         default:  
  141.             if (childCount == 0) {  
  142.                 if (!mStackFromBottom) {  
  143.                     final int position = lookForSelectablePosition(0, true);  
  144.                     setSelectedPositionInt(position);  
  145.                     sel = fillFromTop(childrenTop);  
  146.                 } else {  
  147.                     final int position = lookForSelectablePosition(mItemCount - 1, false);  
  148.                     setSelectedPositionInt(position);  
  149.                     sel = fillUp(mItemCount - 1, childrenBottom);  
  150.                 }  
  151.             } else {  
  152.                 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {  
  153.                     sel = fillSpecific(mSelectedPosition,  
  154.                             oldSel == null ? childrenTop : oldSel.getTop());  
  155.                 } else if (mFirstPosition < mItemCount) {  
  156.                     sel = fillSpecific(mFirstPosition,  
  157.                             oldFirst == null ? childrenTop : oldFirst.getTop());  
  158.                 } else {  
  159.                     sel = fillSpecific(0, childrenTop);  
  160.                 }  
  161.             }  
  162.             break;  
  163.         }  
  164.         // Flush any cached views that did not get reused above  
  165.         recycleBin.scrapActiveViews();  
  166.         if (sel != null) {  
  167.             // the current selected item should get focus if items  
  168.             // are focusable  
  169.             if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {  
  170.                 final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&  
  171.                         focusLayoutRestoreView.requestFocus()) || sel.requestFocus();  
  172.                 if (!focusWasTaken) {  
  173.                     // selected item didn't take focus, fine, but still want  
  174.                     // to make sure something else outside of the selected view  
  175.                     // has focus  
  176.                     final View focused = getFocusedChild();  
  177.                     if (focused != null) {  
  178.                         focused.clearFocus();  
  179.                     }  
  180.                     positionSelector(sel);  
  181.                 } else {  
  182.                     sel.setSelected(false);  
  183.                     mSelectorRect.setEmpty();  
  184.                 }  
  185.             } else {  
  186.                 positionSelector(sel);  
  187.             }  
  188.             mSelectedTop = sel.getTop();  
  189.         } else {  
  190.             if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {  
  191.                 View child = getChildAt(mMotionPosition - mFirstPosition);  
  192.                 if (child != null) positionSelector(child);  
  193.             } else {  
  194.                 mSelectedTop = 0;  
  195.                 mSelectorRect.setEmpty();  
  196.             }  
  197.             // even if there is not selected position, we may need to restore  
  198.             // focus (i.e. something focusable in touch mode)  
  199.             if (hasFocus() && focusLayoutRestoreView != null) {  
  200.                 focusLayoutRestoreView.requestFocus();  
  201.             }  
  202.         }  
  203.         // tell focus view we are done mucking with it, if it is still in  
  204.         // our view hierarchy.  
  205.         if (focusLayoutRestoreView != null  
  206.                 && focusLayoutRestoreView.getWindowToken() != null) {  
  207.             focusLayoutRestoreView.onFinishTemporaryDetach();  
  208.         }  
  209.         mLayoutMode = LAYOUT_NORMAL;  
  210.         mDataChanged = false;  
  211.         mNeedSync = false;  
  212.         setNextSelectedPositionInt(mSelectedPosition);  
  213.         updateScrollIndicators();  
  214.         if (mItemCount > 0) {  
  215.             checkSelectionChanged();  
  216.         }  
  217.         invokeOnItemScrollListener();  
  218.     } finally {  
  219.         if (!blockLayoutRequests) {  
  220.             mBlockLayoutRequests = false;  
  221.         }  
  222.     }  
  223. }  

一樣仍是在第19行,調用getChildCount()方法來獲取子View的數量,只不過如今獲得的值不會再是0了,而是ListView中一屏能夠顯示的子View數量,由於咱們剛剛在第一次Layout過程中向ListView添加了這麼多的子View。下面在第90行調用了RecycleBin的fillActiveViews()方法,此次效果可就不同了,由於目前ListView中已經有子View了,這樣全部的子View都會被緩存到RecycleBin的mActiveViews數組當中,後面將會用到它們。

接下來將會是很是很是重要的一個操做,在第113行調用了detachAllViewsFromParent()方法。這個方法會將全部ListView當中的子View所有清除掉,從而保證第二次Layout過程不會產生一份重複的數據。那有的朋友可能會問了,這樣把已經加載好的View又清除掉,待會還要再從新加載一遍,這不是嚴重影響效率嗎?不用擔憂,還記得咱們剛剛調用了RecycleBin的fillActiveViews()方法來緩存子View嗎,待會兒將會直接使用這些緩存好的View來進行加載,而並不會從新執行一遍inflate過程,所以效率方面並不會有什麼明顯的影響。

那麼咱們接着看,在第141行的判斷邏輯當中,因爲再也不等於0了,所以會進入到else語句當中。而else語句中又有三個邏輯判斷,第一個邏輯判斷不成立,由於默認狀況下咱們沒有選中任何子元素,mSelectedPosition應該等於-1。第二個邏輯判斷一般是成立的,由於mFirstPosition的值一開始是等於0的,只要adapter中的數據大於0條件就成立。那麼進入到fillSpecific()方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Put a specific item at a specific location on the screen and then build 
  3.  * up and down from there. 
  4.  * 
  5.  * @param position The reference view to use as the starting point 
  6.  * @param top Pixel offset from the top of this view to the top of the 
  7.  *        reference view. 
  8.  * 
  9.  * @return The selected view, or null if the selected view is outside the 
  10.  *         visible area. 
  11.  */  
  12. private View fillSpecific(int position, int top) {  
  13.     boolean tempIsSelected = position == mSelectedPosition;  
  14.     View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);  
  15.     // Possibly changed again in fillUp if we add rows above this one.  
  16.     mFirstPosition = position;  
  17.     View above;  
  18.     View below;  
  19.     final int dividerHeight = mDividerHeight;  
  20.     if (!mStackFromBottom) {  
  21.         above = fillUp(position - 1, temp.getTop() - dividerHeight);  
  22.         // This will correct for the top of the first view not touching the top of the list  
  23.         adjustViewsUpOrDown();  
  24.         below = fillDown(position + 1, temp.getBottom() + dividerHeight);  
  25.         int childCount = getChildCount();  
  26.         if (childCount > 0) {  
  27.             correctTooHigh(childCount);  
  28.         }  
  29.     } else {  
  30.         below = fillDown(position + 1, temp.getBottom() + dividerHeight);  
  31.         // This will correct for the bottom of the last view not touching the bottom of the list  
  32.         adjustViewsUpOrDown();  
  33.         above = fillUp(position - 1, temp.getTop() - dividerHeight);  
  34.         int childCount = getChildCount();  
  35.         if (childCount > 0) {  
  36.              correctTooLow(childCount);  
  37.         }  
  38.     }  
  39.     if (tempIsSelected) {  
  40.         return temp;  
  41.     } else if (above != null) {  
  42.         return above;  
  43.     } else {  
  44.         return below;  
  45.     }  
  46. }  

fillSpecific()這算是一個新方法了,不過其實它和fillUp()、fillDown()方法功能也是差很少的,主要的區別在於,fillSpecific()方法會優先將指定位置的子View先加載到屏幕上,而後再加載該子View往上以及往下的其它子View。那麼因爲這裏咱們傳入的position就是第一個子View的位置,因而fillSpecific()方法的做用就基本上和fillDown()方法是差很少的了,這裏咱們就不去關注太多它的細節,而是將精力放在makeAndAddView()方法上面。再次回到makeAndAddView()方法,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

仍然仍是在第19行嘗試從RecycleBin當中獲取Active View,然而此次就必定能夠獲取到了,由於前面咱們調用了RecycleBin的fillActiveViews()方法來緩存子View。那麼既然如此,就不會再進入到第28行的obtainView()方法,而是會直接進入setupChild()方法當中,這樣也省去了不少時間,由於若是在obtainView()方法中又要去infalte佈局的話,那麼ListView的初始加載效率就大大下降了。

注意在第23行,setupChild()方法的最後一個參數傳入的是true,這個參數代表當前的View是以前被回收過的,那麼咱們再次回到setupChild()方法當中:

[java]  view plain  copy
 
  1. /** 
  2.  * Add a view as a child and make sure it is measured (if necessary) and 
  3.  * positioned properly. 
  4.  * 
  5.  * @param child The view to add 
  6.  * @param position The position of this child 
  7.  * @param y The y position relative to which this view will be positioned 
  8.  * @param flowDown If true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @param recycled Has this view been pulled from the recycle bin? If so it 
  13.  *        does not need to be remeasured. 
  14.  */  
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,  
  16.         boolean selected, boolean recycled) {  
  17.     final boolean isSelected = selected && shouldShowSelector();  
  18.     final boolean updateChildSelected = isSelected != child.isSelected();  
  19.     final int mode = mTouchMode;  
  20.     final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&  
  21.             mMotionPosition == position;  
  22.     final boolean updateChildPressed = isPressed != child.isPressed();  
  23.     final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();  
  24.     // Respect layout params that are already in the view. Otherwise make some up...  
  25.     // noinspection unchecked  
  26.     AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();  
  27.     if (p == null) {  
  28.         p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,  
  29.                 ViewGroup.LayoutParams.WRAP_CONTENT, 0);  
  30.     }  
  31.     p.viewType = mAdapter.getItemViewType(position);  
  32.     if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&  
  33.             p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {  
  34.         attachViewToParent(child, flowDown ? -1 : 0, p);  
  35.     } else {  
  36.         p.forceAdd = false;  
  37.         if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {  
  38.             p.recycledHeaderFooter = true;  
  39.         }  
  40.         addViewInLayout(child, flowDown ? -1 : 0, p, true);  
  41.     }  
  42.     if (updateChildSelected) {  
  43.         child.setSelected(isSelected);  
  44.     }  
  45.     if (updateChildPressed) {  
  46.         child.setPressed(isPressed);  
  47.     }  
  48.     if (needToMeasure) {  
  49.         int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,  
  50.                 mListPadding.left + mListPadding.right, p.width);  
  51.         int lpHeight = p.height;  
  52.         int childHeightSpec;  
  53.         if (lpHeight > 0) {  
  54.             childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);  
  55.         } else {  
  56.             childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);  
  57.         }  
  58.         child.measure(childWidthSpec, childHeightSpec);  
  59.     } else {  
  60.         cleanupLayoutState(child);  
  61.     }  
  62.     final int w = child.getMeasuredWidth();  
  63.     final int h = child.getMeasuredHeight();  
  64.     final int childTop = flowDown ? y : y - h;  
  65.     if (needToMeasure) {  
  66.         final int childRight = childrenLeft + w;  
  67.         final int childBottom = childTop + h;  
  68.         child.layout(childrenLeft, childTop, childRight, childBottom);  
  69.     } else {  
  70.         child.offsetLeftAndRight(childrenLeft - child.getLeft());  
  71.         child.offsetTopAndBottom(childTop - child.getTop());  
  72.     }  
  73.     if (mCachingStarted && !child.isDrawingCacheEnabled()) {  
  74.         child.setDrawingCacheEnabled(true);  
  75.     }  
  76. }  

能夠看到,setupChild()方法的最後一個參數是recycled,而後在第32行會對這個變量進行判斷,因爲recycled如今是true,因此會執行attachViewToParent()方法,而第一次Layout過程則是執行的else語句中的addViewInLayout()方法。這兩個方法最大的區別在於,若是咱們須要向ViewGroup中添加一個新的子View,應該調用addViewInLayout()方法,而若是是想要將一個以前detach的View從新attach到ViewGroup上,就應該調用attachViewToParent()方法。那麼因爲前面在layoutChildren()方法當中調用了detachAllViewsFromParent()方法,這樣ListView中全部的子View都是處於detach狀態的,因此這裏attachViewToParent()方法是正確的選擇。

 

經歷了這樣一個detach又attach的過程,ListView中全部的子View又均可以正常顯示出來了,那麼第二次Layout過程結束。

滑動加載更多數據

經歷了兩次Layout過程,雖然說咱們已經能夠在ListView中看到內容了,然而關於ListView最神奇的部分咱們卻尚未接觸到,由於目前ListView中只是加載並顯示了第一屏的數據而已。好比說咱們的Adapter當中有1000條數據,可是第一屏只顯示了10條,ListView中也只有10個子View而已,那麼剩下的990是怎樣工做並顯示到界面上的呢?這就要看一下ListView滑動部分的源碼了,由於咱們是經過手指滑動來顯示更多數據的。

因爲滑動部分的機制是屬於通用型的,即ListView和GridView都會使用一樣的機制,所以這部分代碼就確定是寫在AbsListView當中的了。那麼監聽觸控事件是在onTouchEvent()方法當中進行的,咱們就來看一下AbsListView中的這個方法:

[java]  view plain  copy
 
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent ev) {  
  3.     if (!isEnabled()) {  
  4.         // A disabled view that is clickable still consumes the touch  
  5.         // events, it just doesn't respond to them.  
  6.         return isClickable() || isLongClickable();  
  7.     }  
  8.     final int action = ev.getAction();  
  9.     View v;  
  10.     int deltaY;  
  11.     if (mVelocityTracker == null) {  
  12.         mVelocityTracker = VelocityTracker.obtain();  
  13.     }  
  14.     mVelocityTracker.addMovement(ev);  
  15.     switch (action & MotionEvent.ACTION_MASK) {  
  16.     case MotionEvent.ACTION_DOWN: {  
  17.         mActivePointerId = ev.getPointerId(0);  
  18.         final int x = (int) ev.getX();  
  19.         final int y = (int) ev.getY();  
  20.         int motionPosition = pointToPosition(x, y);  
  21.         if (!mDataChanged) {  
  22.             if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)  
  23.                     && (getAdapter().isEnabled(motionPosition))) {  
  24.                 // User clicked on an actual view (and was not stopping a  
  25.                 // fling). It might be a  
  26.                 // click or a scroll. Assume it is a click until proven  
  27.                 // otherwise  
  28.                 mTouchMode = TOUCH_MODE_DOWN;  
  29.                 // FIXME Debounce  
  30.                 if (mPendingCheckForTap == null) {  
  31.                     mPendingCheckForTap = new CheckForTap();  
  32.                 }  
  33.                 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
  34.             } else {  
  35.                 if (ev.getEdgeFlags() != 0 && motionPosition < 0) {  
  36.                     // If we couldn't find a view to click on, but the down  
  37.                     // event was touching  
  38.                     // the edge, we will bail out and try again. This allows  
  39.                     // the edge correcting  
  40.                     // code in ViewRoot to try to find a nearby view to  
  41.                     // select  
  42.                     return false;  
  43.                 }  
  44.   
  45.                 if (mTouchMode == TOUCH_MODE_FLING) {  
  46.                     // Stopped a fling. It is a scroll.  
  47.                     createScrollingCache();  
  48.                     mTouchMode = TOUCH_MODE_SCROLL;  
  49.                     mMotionCorrection = 0;  
  50.                     motionPosition = findMotionRow(y);  
  51.                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);  
  52.                 }  
  53.             }  
  54.         }  
  55.         if (motionPosition >= 0) {  
  56.             // Remember where the motion event started  
  57.             v = getChildAt(motionPosition - mFirstPosition);  
  58.             mMotionViewOriginalTop = v.getTop();  
  59.         }  
  60.         mMotionX = x;  
  61.         mMotionY = y;  
  62.         mMotionPosition = motionPosition;  
  63.         mLastY = Integer.MIN_VALUE;  
  64.         break;  
  65.     }  
  66.     case MotionEvent.ACTION_MOVE: {  
  67.         final int pointerIndex = ev.findPointerIndex(mActivePointerId);  
  68.         final int y = (int) ev.getY(pointerIndex);  
  69.         deltaY = y - mMotionY;  
  70.         switch (mTouchMode) {  
  71.         case TOUCH_MODE_DOWN:  
  72.         case TOUCH_MODE_TAP:  
  73.         case TOUCH_MODE_DONE_WAITING:  
  74.             // Check if we have moved far enough that it looks more like a  
  75.             // scroll than a tap  
  76.             startScrollIfNeeded(deltaY);  
  77.             break;  
  78.         case TOUCH_MODE_SCROLL:  
  79.             if (PROFILE_SCROLLING) {  
  80.                 if (!mScrollProfilingStarted) {  
  81.                     Debug.startMethodTracing("AbsListViewScroll");  
  82.                     mScrollProfilingStarted = true;  
  83.                 }  
  84.             }  
  85.             if (y != mLastY) {  
  86.                 deltaY -= mMotionCorrection;  
  87.                 int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;  
  88.                 // No need to do all this work if we're not going to move  
  89.                 // anyway  
  90.                 boolean atEdge = false;  
  91.                 if (incrementalDeltaY != 0) {  
  92.                     atEdge = trackMotionScroll(deltaY, incrementalDeltaY);  
  93.                 }  
  94.                 // Check to see if we have bumped into the scroll limit  
  95.                 if (atEdge && getChildCount() > 0) {  
  96.                     // Treat this like we're starting a new scroll from the  
  97.                     // current  
  98.                     // position. This will let the user start scrolling back  
  99.                     // into  
  100.                     // content immediately rather than needing to scroll  
  101.                     // back to the  
  102.                     // point where they hit the limit first.  
  103.                     int motionPosition = findMotionRow(y);  
  104.                     if (motionPosition >= 0) {  
  105.                         final View motionView = getChildAt(motionPosition - mFirstPosition);  
  106.                         mMotionViewOriginalTop = motionView.getTop();  
  107.                     }  
  108.                     mMotionY = y;  
  109.                     mMotionPosition = motionPosition;  
  110.                     invalidate();  
  111.                 }  
  112.                 mLastY = y;  
  113.             }  
  114.             break;  
  115.         }  
  116.         break;  
  117.     }  
  118.     case MotionEvent.ACTION_UP: {  
  119.         switch (mTouchMode) {  
  120.         case TOUCH_MODE_DOWN:  
  121.         case TOUCH_MODE_TAP:  
  122.         case TOUCH_MODE_DONE_WAITING:  
  123.             final int motionPosition = mMotionPosition;  
  124.             final View child = getChildAt(motionPosition - mFirstPosition);  
  125.             if (child != null && !child.hasFocusable()) {  
  126.                 if (mTouchMode != TOUCH_MODE_DOWN) {  
  127.                     child.setPressed(false);  
  128.                 }  
  129.                 if (mPerformClick == null) {  
  130.                     mPerformClick = new PerformClick();  
  131.                 }  
  132.                 final AbsListView.PerformClick performClick = mPerformClick;  
  133.                 performClick.mChild = child;  
  134.                 performClick.mClickMotionPosition = motionPosition;  
  135.                 performClick.rememberWindowAttachCount();  
  136.                 mResurrectToPosition = motionPosition;  
  137.                 if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {  
  138.                     final Handler handler = getHandler();  
  139.                     if (handler != null) {  
  140.                         handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap  
  141.                                 : mPendingCheckForLongPress);  
  142.                     }  
  143.                     mLayoutMode = LAYOUT_NORMAL;  
  144.                     if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {  
  145.                         mTouchMode = TOUCH_MODE_TAP;  
  146.                         setSelectedPositionInt(mMotionPosition);  
  147.                         layoutChildren();  
  148.                         child.setPressed(true);  
  149.                         positionSelector(child);  
  150.                         setPressed(true);  
  151.                         if (mSelector != null) {  
  152.                             Drawable d = mSelector.getCurrent();  
  153.                             if (d != null && d instanceof TransitionDrawable) {  
  154.                                 ((TransitionDrawable) d).resetTransition();  
  155.                             }  
  156.                         }  
  157.                         postDelayed(new Runnable() {  
  158.                             public void run() {  
  159.                                 child.setPressed(false);  
  160.                                 setPressed(false);  
  161.                                 if (!mDataChanged) {  
  162.                                     post(performClick);  
  163.                                 }  
  164.                                 mTouchMode = TOUCH_MODE_REST;  
  165.                             }  
  166.                         }, ViewConfiguration.getPressedStateDuration());  
  167.                     } else {  
  168.                         mTouchMode = TOUCH_MODE_REST;  
  169.                     }  
  170.                     return true;  
  171.                 } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {  
  172.                     post(performClick);  
  173.                 }  
  174.             }  
  175.             mTouchMode = TOUCH_MODE_REST;  
  176.             break;  
  177.         case TOUCH_MODE_SCROLL:  
  178.             final int childCount = getChildCount();  
  179.             if (childCount > 0) {  
  180.                 if (mFirstPosition == 0  
  181.                         && getChildAt(0).getTop() >= mListPadding.top  
  182.                         && mFirstPosition + childCount < mItemCount  
  183.                         && getChildAt(childCount - 1).getBottom() <= getHeight()  
  184.                                 - mListPadding.bottom) {  
  185.                     mTouchMode = TOUCH_MODE_REST;  
  186.                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  187.                 } else {  
  188.                     final VelocityTracker velocityTracker = mVelocityTracker;  
  189.                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  190.                     final int initialVelocity = (int) velocityTracker  
  191.                             .getYVelocity(mActivePointerId);  
  192.                     if (Math.abs(initialVelocity) > mMinimumVelocity) {  
  193.                         if (mFlingRunnable == null) {  
  194.                             mFlingRunnable = new FlingRunnable();  
  195.                         }  
  196.                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);  
  197.                         mFlingRunnable.start(-initialVelocity);  
  198.                     } else {  
  199.                         mTouchMode = TOUCH_MODE_REST;  
  200.                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  201.                     }  
  202.                 }  
  203.             } else {  
  204.                 mTouchMode = TOUCH_MODE_REST;  
  205.                 reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);  
  206.             }  
  207.             break;  
  208.         }  
  209.         setPressed(false);  
  210.         // Need to redraw since we probably aren't drawing the selector  
  211.         // anymore  
  212.         invalidate();  
  213.         final Handler handler = getHandler();  
  214.         if (handler != null) {  
  215.             handler.removeCallbacks(mPendingCheckForLongPress);  
  216.         }  
  217.         if (mVelocityTracker != null) {  
  218.             mVelocityTracker.recycle();  
  219.             mVelocityTracker = null;  
  220.         }  
  221.         mActivePointerId = INVALID_POINTER;  
  222.         if (PROFILE_SCROLLING) {  
  223.             if (mScrollProfilingStarted) {  
  224.                 Debug.stopMethodTracing();  
  225.                 mScrollProfilingStarted = false;  
  226.             }  
  227.         }  
  228.         break;  
  229.     }  
  230.     case MotionEvent.ACTION_CANCEL: {  
  231.         mTouchMode = TOUCH_MODE_REST;  
  232.         setPressed(false);  
  233.         View motionView = this.getChildAt(mMotionPosition - mFirstPosition);  
  234.         if (motionView != null) {  
  235.             motionView.setPressed(false);  
  236.         }  
  237.         clearScrollingCache();  
  238.         final Handler handler = getHandler();  
  239.         if (handler != null) {  
  240.             handler.removeCallbacks(mPendingCheckForLongPress);  
  241.         }  
  242.         if (mVelocityTracker != null) {  
  243.             mVelocityTracker.recycle();  
  244.             mVelocityTracker = null;  
  245.         }  
  246.         mActivePointerId = INVALID_POINTER;  
  247.         break;  
  248.     }  
  249.     case MotionEvent.ACTION_POINTER_UP: {  
  250.         onSecondaryPointerUp(ev);  
  251.         final int x = mMotionX;  
  252.         final int y = mMotionY;  
  253.         final int motionPosition = pointToPosition(x, y);  
  254.         if (motionPosition >= 0) {  
  255.             // Remember where the motion event started  
  256.             v = getChildAt(motionPosition - mFirstPosition);  
  257.             mMotionViewOriginalTop = v.getTop();  
  258.             mMotionPosition = motionPosition;  
  259.         }  
  260.         mLastY = y;  
  261.         break;  
  262.     }  
  263.     }  
  264.     return true;  
  265. }  

這個方法中的代碼就很是多了,由於它所處理的邏輯也很是多,要監聽各類各樣的觸屏事件。可是咱們目前所關心的就只有手指在屏幕上滑動這一個事件而已,對應的是ACTION_MOVE這個動做,那麼咱們就只看這部分代碼就能夠了。

能夠看到,ACTION_MOVE這個case裏面又嵌套了一個switch語句,是根據當前的TouchMode來選擇的。那這裏我能夠直接告訴你們,當手指在屏幕上滑動時,TouchMode是等於TOUCH_MODE_SCROLL這個值的,至於爲何那又要牽扯到另外的好幾個方法,這裏限於篇幅緣由就再也不展開講解了,喜歡尋根究底的朋友們能夠本身去源碼裏找一找緣由。

這樣的話,代碼就應該會走到第78行的這個case裏面去了,在這個case當中並無什麼太多須要注意的東西,惟一一點很是重要的就是第92行調用的trackMotionScroll()方法,至關於咱們手指只要在屏幕上稍微有一點點移動,這個方法就會被調用,而若是是正常在屏幕上滑動的話,那麼這個方法就會被調用不少次。那麼咱們進入到這個方法中瞧一瞧,代碼以下所示:

[java]  view plain  copy
 
  1. boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {  
  2.     final int childCount = getChildCount();  
  3.     if (childCount == 0) {  
  4.         return true;  
  5.     }  
  6.     final int firstTop = getChildAt(0).getTop();  
  7.     final int lastBottom = getChildAt(childCount - 1).getBottom();  
  8.     final Rect listPadding = mListPadding;  
  9.     final int spaceAbove = listPadding.top - firstTop;  
  10.     final int end = getHeight() - listPadding.bottom;  
  11.     final int spaceBelow = lastBottom - end;  
  12.     final int height = getHeight() - getPaddingBottom() - getPaddingTop();  
  13.     if (deltaY < 0) {  
  14.         deltaY = Math.max(-(height - 1), deltaY);  
  15.     } else {  
  16.         deltaY = Math.min(height - 1, deltaY);  
  17.     }  
  18.     if (incrementalDeltaY < 0) {  
  19.         incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);  
  20.     } else {  
  21.         incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);  
  22.     }  
  23.     final int firstPosition = mFirstPosition;  
  24.     if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {  
  25.         // Don't need to move views down if the top of the first position  
  26.         // is already visible  
  27.         return true;  
  28.     }  
  29.     if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {  
  30.         // Don't need to move views up if the bottom of the last position  
  31.         // is already visible  
  32.         return true;  
  33.     }  
  34.     final boolean down = incrementalDeltaY < 0;  
  35.     final boolean inTouchMode = isInTouchMode();  
  36.     if (inTouchMode) {  
  37.         hideSelector();  
  38.     }  
  39.     final int headerViewsCount = getHeaderViewsCount();  
  40.     final int footerViewsStart = mItemCount - getFooterViewsCount();  
  41.     int start = 0;  
  42.     int count = 0;  
  43.     if (down) {  
  44.         final int top = listPadding.top - incrementalDeltaY;  
  45.         for (int i = 0; i < childCount; i++) {  
  46.             final View child = getChildAt(i);  
  47.             if (child.getBottom() >= top) {  
  48.                 break;  
  49.             } else {  
  50.                 count++;  
  51.                 int position = firstPosition + i;  
  52.                 if (position >= headerViewsCount && position < footerViewsStart) {  
  53.                     mRecycler.addScrapView(child);  
  54.                 }  
  55.             }  
  56.         }  
  57.     } else {  
  58.         final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;  
  59.         for (int i = childCount - 1; i >= 0; i--) {  
  60.             final View child = getChildAt(i);  
  61.             if (child.getTop() <= bottom) {  
  62.                 break;  
  63.             } else {  
  64.                 start = i;  
  65.                 count++;  
  66.                 int position = firstPosition + i;  
  67.                 if (position >= headerViewsCount && position < footerViewsStart) {  
  68.                     mRecycler.addScrapView(child);  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73.     mMotionViewNewTop = mMotionViewOriginalTop + deltaY;  
  74.     mBlockLayoutRequests = true;  
  75.     if (count > 0) {  
  76.         detachViewsFromParent(start, count);  
  77.     }  
  78.     offsetChildrenTopAndBottom(incrementalDeltaY);  
  79.     if (down) {  
  80.         mFirstPosition += count;  
  81.     }  
  82.     invalidate();  
  83.     final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);  
  84.     if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {  
  85.         fillGap(down);  
  86.     }  
  87.     if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {  
  88.         final int childIndex = mSelectedPosition - mFirstPosition;  
  89.         if (childIndex >= 0 && childIndex < getChildCount()) {  
  90.             positionSelector(getChildAt(childIndex));  
  91.         }  
  92.     }  
  93.     mBlockLayoutRequests = false;  
  94.     invokeOnItemScrollListener();  
  95.     awakenScrollBars();  
  96.     return false;  
  97. }  

這個方法接收兩個參數,deltaY表示從手指按下時的位置到當前手指位置的距離,incrementalDeltaY則表示據上次觸發event事件手指在Y方向上位置的改變量,那麼其實咱們就能夠經過incrementalDeltaY的正負值狀況來判斷用戶是向上仍是向下滑動的了。如第34行代碼所示,若是incrementalDeltaY小於0,說明是向下滑動,不然就是向上滑動。

下面將會進行一個邊界值檢測的過程,能夠看到,從第43行開始,當ListView向下滑動的時候,就會進入一個for循環當中,從上往下依次獲取子View,第47行當中,若是該子View的bottom值已經小於top值了,就說明這個子View已經移出屏幕了,因此會調用RecycleBin的addScrapView()方法將這個View加入到廢棄緩存當中,並將count計數器加1,計數器用於記錄有多少個子View被移出了屏幕。那麼若是是ListView向上滑動的話,其實過程是基本相同的,只不過變成了從下往上依次獲取子View,而後判斷該子View的top值是否是大於bottom值了,若是大於的話說明子View已經移出了屏幕,一樣把它加入到廢棄緩存中,並將計數器加1。

接下來在第76行,會根據當前計數器的值來進行一個detach操做,它的做用就是把全部移出屏幕的子View所有detach掉,在ListView的概念當中,全部看不到的View就沒有必要爲它進行保存,由於屏幕外還有成百上千條數據等着顯示呢,一個好的回收策略才能保證ListView的高性能和高效率。緊接着在第78行調用了offsetChildrenTopAndBottom()方法,並將incrementalDeltaY做爲參數傳入,這個方法的做用是讓ListView中全部的子View都按照傳入的參數值進行相應的偏移,這樣就實現了隨着手指的拖動,ListView的內容也會隨着滾動的效果。

而後在第84行會進行判斷,若是ListView中最後一個View的底部已經移入了屏幕,或者ListView中第一個View的頂部移入了屏幕,就會調用fillGap()方法,那麼所以咱們就能夠猜出fillGap()方法是用來加載屏幕外數據的,進入到這個方法中瞧一瞧,以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Fills the gap left open by a touch-scroll. During a touch scroll, 
  3.  * children that remain on screen are shifted and the other ones are 
  4.  * discarded. The role of this method is to fill the gap thus created by 
  5.  * performing a partial layout in the empty space. 
  6.  *  
  7.  * @param down 
  8.  *            true if the scroll is going down, false if it is going up 
  9.  */  
  10. abstract void fillGap(boolean down);  

OK,AbsListView中的fillGap()是一個抽象方法,那麼咱們馬上就可以想到,它的具體實現確定是在ListView中完成的了。回到ListView當中,fillGap()方法的代碼以下所示:

[java]  view plain  copy
 
  1. void fillGap(boolean down) {  
  2.     final int count = getChildCount();  
  3.     if (down) {  
  4.         final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight :  
  5.                 getListPaddingTop();  
  6.         fillDown(mFirstPosition + count, startOffset);  
  7.         correctTooHigh(getChildCount());  
  8.     } else {  
  9.         final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :  
  10.                 getHeight() - getListPaddingBottom();  
  11.         fillUp(mFirstPosition - 1, startOffset);  
  12.         correctTooLow(getChildCount());  
  13.     }  
  14. }  

down參數用於表示ListView是向下滑動仍是向上滑動的,能夠看到,若是是向下滑動的話就會調用fillDown()方法,而若是是向上滑動的話就會調用fillUp()方法。那麼這兩個方法咱們都已經很是熟悉了,內部都是經過一個循環來去對ListView進行填充,因此這兩個方法咱們就不看了,可是填充ListView會經過調用makeAndAddView()方法來完成,又是makeAndAddView()方法,但此次的邏輯再次不一樣了,因此咱們仍是回到這個方法瞧一瞧:

[java]  view plain  copy
 
  1. /** 
  2.  * Obtain the view and add it to our list of children. The view can be made 
  3.  * fresh, converted from an unused view, or used as is if it was in the 
  4.  * recycle bin. 
  5.  * 
  6.  * @param position Logical position in the list 
  7.  * @param y Top or bottom edge of the view to add 
  8.  * @param flow If flow is true, align top edge to y. If false, align bottom 
  9.  *        edge to y. 
  10.  * @param childrenLeft Left edge where children should be positioned 
  11.  * @param selected Is this position selected? 
  12.  * @return View that was added 
  13.  */  
  14. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,  
  15.         boolean selected) {  
  16.     View child;  
  17.     if (!mDataChanged) {  
  18.         // Try to use an exsiting view for this position  
  19.         child = mRecycler.getActiveView(position);  
  20.         if (child != null) {  
  21.             // Found it -- we're using an existing child  
  22.             // This just needs to be positioned  
  23.             setupChild(child, position, y, flow, childrenLeft, selected, true);  
  24.             return child;  
  25.         }  
  26.     }  
  27.     // Make a new view for this position, or convert an unused view if possible  
  28.     child = obtainView(position, mIsScrap);  
  29.     // This needs to be positioned and measured  
  30.     setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  
  31.     return child;  
  32. }  

無論怎麼說,這裏首先仍然是會嘗試調用RecycleBin的getActiveView()方法來獲取子佈局,只不過確定是獲取不到的了,由於在第二次Layout過程當中咱們已經從mActiveViews中獲取過了數據,而根據RecycleBin的機制,mActiveViews是不可以重複利用的,所以這裏返回的值確定是null。

既然getActiveView()方法返回的值是null,那麼就仍是會走到第28行的obtainView()方法當中,代碼以下所示:

[java]  view plain  copy
 
  1. /** 
  2.  * Get a view and have it show the data associated with the specified 
  3.  * position. This is called when we have already discovered that the view is 
  4.  * not available for reuse in the recycle bin. The only choices left are 
  5.  * converting an old view or making a new one. 
  6.  *  
  7.  * @param position 
  8.  *            The position to display 
  9.  * @param isScrap 
  10.  *            Array of at least 1 boolean, the first entry will become true 
  11.  *            if the returned view was taken from the scrap heap, false if 
  12.  *            otherwise. 
  13.  *  
  14.  * @return A view displaying the data associated with the specified position 
  15.  */  
  16. View obtainView(int position, boolean[] isScrap) {  
  17.     isScrap[0] = false;  
  18.     View scrapView;  
  19.     scrapView = mRecycler.getScrapView(position);  
  20.     View child;  
  21.     if (scrapView != null) {  
  22.         child = mAdapter.getView(position, scrapView, this);  
  23.         if (child != scrapView) {  
  24.             mRecycler.addScrapView(scrapView);  
  25.             if (mCacheColorHint != 0) {  
  26.                 child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  27.             }  
  28.         } else {  
  29.             isScrap[0] = true;  
  30.             dispatchFinishTemporaryDetach(child);  
  31.         }  
  32.     } else {  
  33.         child = mAdapter.getView(position, null, this);  
  34.         if (mCacheColorHint != 0) {  
  35.             child.setDrawingCacheBackgroundColor(mCacheColorHint);  
  36.         }  
  37.     }  
  38.     return child;  
  39. }  

這裏在第19行會調用RecyleBin的getScrapView()方法來嘗試從廢棄緩存中獲取一個View,那麼廢棄緩存有沒有View呢?固然有,由於剛纔在trackMotionScroll()方法中咱們就已經看到了,一旦有任何子View被移出了屏幕,就會將它加入到廢棄緩存中,而從obtainView()方法中的邏輯來看,一旦有新的數據須要顯示到屏幕上,就會嘗試從廢棄緩存中獲取View。因此它們之間就造成了一個生產者和消費者的模式,那麼ListView神奇的地方也就在這裏體現出來了,無論你有任意多條數據須要顯示,ListView中的子View其實來來回回就那麼幾個,移出屏幕的子View會很快被移入屏幕的數據從新利用起來,於是無論咱們加載多少數據都不會出現OOM的狀況,甚至內存都不會有所增長。

那麼另外還有一點是須要你們留意的,這裏獲取到了一個scrapView,而後咱們在第22行將它做爲第二個參數傳入到了Adapter的getView()方法當中。那麼第二個參數是什麼意思呢?咱們再次看一下一個簡單的getView()方法示例:

 

[java]  view plain  copy
 
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     Fruit fruit = getItem(position);  
  4.     View view;  
  5.     if (convertView == null) {  
  6.         view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  7.     } else {  
  8.         view = convertView;  
  9.     }  
  10.     ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  11.     TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  12.     fruitImage.setImageResource(fruit.getImageId());  
  13.     fruitName.setText(fruit.getName());  
  14.     return view;  
  15. }  

第二個參數就是咱們最熟悉的convertView呀,難怪平時咱們在寫getView()方法是要判斷一下convertView是否是等於null,若是等於null才調用inflate()方法來加載佈局,不等於null就能夠直接利用convertView,由於convertView就是咱們之間利用過的View,只不過被移出屏幕後進入到了廢棄緩存中,如今又從新拿出來使用而已。而後咱們只須要把convertView中的數據更新成當前位置上應該顯示的數據,那麼看起來就好像是全新加載出來的一個佈局同樣,這背後的道理你是否是已經徹底搞明白了?

 

以後的代碼又都是咱們熟悉的流程了,從緩存中拿到子View以後再調用setupChild()方法將它從新attach到ListView當中,由於緩存中的View也是以前從ListView中detach掉的,這部分代碼就再也不重複進行分析了。

爲了方便你們理解,這裏我再附上一張圖解說明:

那麼到目前爲止,咱們就把ListView的整個工做流程代碼基本分析結束了,文章比較長,但願你們能夠理解清楚,下篇文章中會講解咱們平時使用ListView時遇到的問題,感興趣的朋友請繼續閱讀 Android ListView異步加載圖片亂序問題,緣由分析及解決方案 。

 

第一時間得到博客更新提醒,以及更多技術信息分享,歡迎關注個人微信公衆號,掃一掃下方二維碼或搜索微信號guolin_blog,便可關注。

那麼另外還有一點是須要你們留意的,這裏獲取到了一個scrapView,而後咱們在第22行將它做爲第二個參數傳入到了Adapter的getView()方法當中。那麼第二個參數是什麼意思呢?咱們再次看一下一個簡單的getView()方法示例:

 

[java]  view plain  copy
 
  1. @Override  
  2. public View getView(int position, View convertView, ViewGroup parent) {  
  3.     Fruit fruit = getItem(position);  
  4.     View view;  
  5.     if (convertView == null) {  
  6.         view = LayoutInflater.from(getContext()).inflate(resourceId, null);  
  7.     } else {  
  8.         view = convertView;  
  9.     }  
  10.     ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);  
  11.     TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);  
  12.     fruitImage.setImageResource(fruit.getImageId());  
  13.     fruitName.setText(fruit.getName());  
  14.     return view;  
  15. }  

第二個參數就是咱們最熟悉的convertView呀,難怪平時咱們在寫getView()方法是要判斷一下convertView是否是等於null,若是等於null才調用inflate()方法來加載佈局,不等於null就能夠直接利用convertView,由於convertView就是咱們之間利用過的View,只不過被移出屏幕後進入到了廢棄緩存中,如今又從新拿出來使用而已。而後咱們只須要把convertView中的數據更新成當前位置上應該顯示的數據,那麼看起來就好像是全新加載出來的一個佈局同樣,這背後的道理你是否是已經徹底搞明白了?

 

以後的代碼又都是咱們熟悉的流程了,從緩存中拿到子View以後再調用setupChild()方法將它從新attach到ListView當中,由於緩存中的View也是以前從ListView中detach掉的,這部分代碼就再也不重複進行分析了。

爲了方便你們理解,這裏我再附上一張圖解說明:

相關文章
相關標籤/搜索