本文主要從源碼的角度分析 ListView 的工作原理和使用方法,如有不正確的地方歡迎大家指正。
Adapter
在分析 ListView 之前,我們先稍微介紹下 Adapter。Adapter 顧名思義就是適配器,他是ListView 和數(shù)據(jù)之間的橋梁,ListView 不會直接和數(shù)據(jù)打交道,而是通過 Adapter 訪問數(shù)據(jù)。我們稍微想一下,如果 ListView 像 TextView 或者ImageView 直接和數(shù)據(jù)交互,會有什么問題呢?因為數(shù)據(jù)千變?nèi)f化,這樣我們就需要寫各式各樣的 XXListView ,復(fù)用度大大降低,ListView 變得臃腫。
我們知道 ListView 是通過 setAdapter(xxx) 方法來實現(xiàn) adapter 和 ListView 的連接,那么我們就從 setAdapter(xxx) 方法開始,來分析 ListView 的工作原理。
setAdapter

在這段代碼中我們著重看這兩個地方 497 行mRecycler.setViewTypeCount(mAdapter.getViewTypeCount())
519 行 requestLayout()
497 行我們看到 RecycleBin 中的 ViewTypeCount 設(shè)置的是 Adapter 的 getViewTypeCount() 的值,具體有什么用我們先留一個小懸念,后邊會具體介紹。
下邊我們具體看下 requestLayout() 方法,該方法會調(diào)用 View 中的? requestLayout(),然后 View 的 requestLayout() 方法又會調(diào)用 ViewParent 的 requestLayout(),ViewParent 是一個接口,其具體實現(xiàn)類是 ViewRootImpl,所以我們來看下 ViewRootImpl 的 requestLayout() 方法

我們看到 requestLayout() 方法中會調(diào)用 scheduleTraversals() ,通過 scheduleTraversals() 會對 View 開始繪制。而繪制一個 View 會有三步流程 onMeasure() 測量一個 View 大??;onLayout() 確定一個 View 的布局;onDraw() 將一個 View 繪制到界面。而 ListView 核心邏輯是在 onLayout() 中。
onLayout
下邊我們來看下 ListView 的 onLayout() 方法,我們發(fā)現(xiàn) ListView 沒有 onLayout() 方法,所以直接看 ListView 的父類 AbsListView 的 onLayout() 方法。

在 onLayout() 方法中會調(diào)用 layoutChildren() 方法,由于 layoutChildren() 方法太長,我們來截取一些核心代碼

首先把 children 存入 RecycleBin 中并 detachAllViews,說到 RecycleBin 我們需要重點介紹下。
RecycleBin
RecycleBin 中有兩級緩存,第一級緩存是 View[] mActiveViews 也就是當(dāng)前屏幕中顯示的 Views,第二級緩存是 ArrayList[] mScrapViews 也就是移出屏幕的 Views。我們看到 mScrapViews 是一個 List<View> 的數(shù)組,而數(shù)組的就是 viewTypeCount,詳見下邊源碼中的 6410 和 6416 行。

下邊我們重點看下 RecycleBin 中 fillActiveViews()、getActiveView()、addScrapView()、getScrapView() 這四個方法。
fillActiveViews

fillActiveViews() 方法邏輯并不復(fù)雜,主要就是保存 Views 到 mActiveViews,并記錄 View 在 ListView 中的邏輯位置。然后我們看下 fillActiveViews() 是在什么地方調(diào)用的,我們發(fā)現(xiàn)只有 layoutChildren() 中會調(diào)用,也就是在 layoutChildren() 時會把當(dāng)前屏幕中的 Views 保存到 mActiveViews 中。
getActiveView

getActiveView() 邏輯就是根據(jù) View 的 position 從 mActiveViews 中取,并把 mActiveViews 中對應(yīng)位置的 View 置空。具體調(diào)用的地方是 makeAndAddView() 方法,makeAndAddView() 方法我們會在后邊具體介紹。
addScrapView

addScrapView() 方法我們重點看 6640 行到 6644 行,如果 viewTypeCount 為 1 則把 View 放到 mCurrentScrap 這個 List<View> 中,否則根據(jù) viewType 放到某個 List<View> 中。addScrapView() 調(diào)用的地方我們看下邊這段代碼

這段代碼的邏輯其實也不復(fù)雜,就是當(dāng)向上或向下滾動時,把滾出屏幕的 View 放到 mScrapViews 中。
getScrapView()


從這兩段代碼我們看到,其實是根據(jù) ViewType 和 position 從 mScrapViews 中取 View 并從 mScrapViews 中移除。結(jié)合 retrieveFromScrap() 代碼我們重點強調(diào)一個事,當(dāng)你 ListView 中有多種 Type 的 View 時必須重寫 Adapter 的 getViewTypeCount() 方法,為什么呢?因為 getViewTypeCount() 默認返回值是 1,這樣所有滑出屏幕的 View 都會存在 mCurrentScrap 中而不會分組來存。當(dāng) getScrapView() 時會遍歷 mCurrentScrap,看 id 或 position 是否匹配,滑出去和滑進來的 View 的 id 或 position 通常是不匹配的,除非你滑出去后又馬上滑進來。這樣就會取 mCurrentScrap 最后一個 item,這樣取出的 scrapView 大概率不是我們要的 View 。

從上邊代碼我們可以看出來,其實 getScrapView() 方法取出的 View 就是 Adapter 的 getView() 方法中的 convertView。
fillDown
我們接著上邊聊 layoutChildren(),下邊會調(diào)用 fillFromTop() 方法,而 fillFromTop() 方法會調(diào)用 fillDown() 方法,我們具體看下 fillDown() 方法。

fillDown 接收兩個參數(shù),pos 表示列表中第一個要繪制的 item 的 position,其對應(yīng)著 Adapter 中的索引,nextTop 表示第一個要繪制的 item 在 ListView 中實際的位置, 即該 item 所對應(yīng)的子 View 的頂部到 ListView 的頂部的像素數(shù)。
首先將 mBottom - mTop 的值作為 end,end 表示 ListView 的高度,然后在 while 循環(huán)中添加子 View,我們先不看 while 循環(huán)的具體條件,先看一下循環(huán)體。在循環(huán)體中,將 pos 和nextTop 傳遞給 makeAndAddView 方法,該方法返回一個 View 作為 child,該方法會創(chuàng)建 View,并把該 View 作為 child 添加到 ListView 的 children 數(shù)組中。然后執(zhí)行 nextTop = child.getBottom() + mDividerHeight,child 的 bottom 值表示的是該 child 的底部到 ListView 頂部的距離,將該 child 的 bottom 作為下一個 child 的 top,也就是說 nextTop 一直保存著下一個 child 的 top 值。
最后調(diào)用 pos++ 實現(xiàn) position 指針下移?,F(xiàn)在我們回過頭來看一下 while 循環(huán)的條件 while (nextTop < end && pos < mItemCount)。
nextTop < end 確保了我們只要將新增的子 View 能夠覆蓋 ListView 的界面就可以了,比如 ListView 的高度最多顯示 10 個子 View,我們沒必要向 ListView 中加入 11 個子 View。
pos < mItemCount 確保了我們新增的子 View 在 Adapter 中都有對應(yīng)的數(shù)據(jù)源 item,比如ListView 的高度最多顯示 10 個子 View,但是我們 Adapter 中一共才有 5 條數(shù)據(jù),這種情況下只能向 ListView 中加入 5 個子 View,從而不能填充滿 ListView 的全部高度。
下邊我們看下循環(huán)體中 makeAndAddView() 方法。
makeAndAddView

makeAndAddView() 先從 activeViews 中,如果取不到則執(zhí)行 obtainView() 。

其實重點就是 2345,2346 兩行,首先從 scrapViews 中取 View 并作為 Adapter 的 getView() 方法的 convertView 參數(shù),執(zhí)行 getView() 。
ok ~~ 到這里我們大致了解了 Adapter、ListView、數(shù)據(jù)源之間是如何相互聯(lián)系的。