原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879java
在Android全部經常使用的原生控件當中,用法最複雜的應該就是ListView了,它專門用於處理那種內容元素不少,手機屏幕沒法展現出全部內容的狀況。ListView可使用列表的形式來展現內容,超出屏幕部分的內容只須要經過手指滑動就能夠移動到屏幕內了。mysql
另外ListView還有一個很是神奇的功能,我相信你們應該都體驗過,即便在ListView中加載很是很是多的數據,好比達到成百上千條甚至更多,ListView都不會發生OOM或者崩潰,並且隨着咱們手指滑動來瀏覽更多數據時,程序所佔用的內存居然都不會跟着增加。那麼ListView是怎麼實現這麼神奇的功能的呢?當初我就抱着學習的心態花了很長時間把ListView的源碼通讀了一遍,基本瞭解了它的工做原理,在感嘆Google大神可以寫出如此精妙代碼的同時我也有所敬畏,由於ListView的代碼量比較大,複雜度也很高,很難用文字表達清楚,因而我就放棄了把它寫成一篇博客的想法。那麼如今回想起來這件事我已經腸子都悔青了,由於沒過幾個月時間我就把當初梳理清晰的源碼又忘的一乾二淨。因而如今我又從新定下心來再次把ListView的源碼重讀了一遍,那麼此次我必定要把它寫成一篇博客,分享給你們的同時也當成我本身的筆記吧。android
首先咱們先來看一下ListView的繼承結構,以下圖所示:算法
能夠看到,ListView的繼承結構仍是至關複雜的,它是直接繼承自的AbsListView,而AbsListView有兩個子實現類,一個是ListView,另外一個就是GridView,所以咱們從這一點就能夠猜出來,ListView和GridView在工做原理和實現上都是有不少共同點的。而後AbsListView又繼承自AdapterView,AdapterView繼承自ViewGroup,後面就是咱們所熟知的了。先把ListView的繼承結構瞭解一下,待會兒有助於咱們更加清晰地分析代碼。sql
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()方法,這個在下面的文章中還會詳細講到。學習
那麼在開始分析ListView的源碼以前,還有一個東西是咱們提早須要瞭解的,就是RecycleBin機制,這個機制也是ListView可以實現成百上千條數據都不會OOM最重要的一個緣由。其實RecycleBin的代碼並很少,只有300行左右,它是寫在AbsListView中的一個內部類,因此全部繼承自AbsListView的子類,也就是ListView和GridView,均可以使用這個機制。那咱們來看一下RecycleBin中的主要代碼,以下所示:
/** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are * those views which were onscreen at the start of a layout. By * construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews * are old views that could potentially be used by the adapter to avoid * allocating views unnecessarily. * * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) * @see android.widget.AbsListView.RecyclerListener */ class RecycleBin { private RecyclerListener mRecyclerListener; /** * The position of the first view stored in mActiveViews. */ private int mFirstActivePosition; /** * Views that were on screen at the start of layout. This array is * populated at the start of layout, and at the end of layout all view * in mActiveViews are moved to mScrapViews. Views in mActiveViews * represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; private int mViewTypeCount; private ArrayList<View> mCurrentScrap; /** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount * The minimum number of views mActiveViews should hold * @param firstActivePosition * The position of the first view that will be stored in * mActiveViews */ void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in // active views. // However, we will NOT place them into scrap views. activeViews[i] = child; } } } /** * Get the view corresponding to the specified position. The view will * be removed from mActiveViews if it is found. * * @param position * The position to look up in mActiveViews * @return The view if it is found, null otherwise */ View getActiveView(int position) { int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >= 0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; return match; } return null; } /** * Put a view into the ScapViews list. These views are unordered. * * @param scrap * The view to add */ void addScrapView(View scrap) { AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } // Don't put header or footer views or views that should be ignored // into the scrap heap int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(scrap, false); } return; } if (mViewTypeCount == 1) { dispatchFinishTemporaryDetach(scrap); mCurrentScrap.add(scrap); } else { dispatchFinishTemporaryDetach(scrap); mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } /** * @return A view from the ScrapViews collection. These are unordered. */ View getScrapView(int position) { ArrayList<View> scrapViews; if (mViewTypeCount == 1) { scrapViews = mCurrentScrap; int size = scrapViews.size(); if (size > 0) { return scrapViews.remove(size - 1); } else { return null; } } else { int whichScrap = mAdapter.getItemViewType(position); if (whichScrap >= 0 && whichScrap < mScrapViews.length) { scrapViews = mScrapViews[whichScrap]; int size = scrapViews.size(); if (size > 0) { return scrapViews.remove(size - 1); } } } return null; } public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } // noinspection unchecked ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList<View>(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; } }
這裏的RecycleBin代碼並不全,我只是把最主要的幾個方法提了出來。那麼咱們先來對這幾個方法進行簡單解讀,這對後面分析ListView的工做原理將會有很大的幫助。
瞭解了RecycleBin中的主要方法以及它們的用處以後,下面就能夠開始來分析ListView的工做原理了,這裏我將仍是按照之前分析源碼的方式來進行,即跟着主線執行流程來逐步閱讀並點到即止,否則的話要是把ListView全部的代碼都貼出來,那麼本篇文章將會很長很長了。
無論怎麼說,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中實現的,代碼以下所示: