一、ListView 的工作原理
Adapter的作用就是ListView界面與數據交互的橋樑,當列表裏面每一項顯示到頁面上時,都會調用Adapter的getView()方法。
系統需要回執ListView時,首先會調用getCount()函數,得到要繪製的這個列表額長度,然後開始從第一行開始繪製,每行的回執方法是調用getView函數。那麼Android是不是爲每一行都會新創建一個View呢?試想加入行數爲幾萬行,內存肯定會爆掉的,所以Android官方早就想到了這一點,在ListView實現中添加了視圖的緩存-Recycler,每當有行移除屏幕的可視區域時,這個被移除的行的View對象就會被添加到Recycler中,也就是在渲染新行時的那個參數convertView。
二、ListView的初始化
疑問一:ListView繪製時是如何獲取每行的View的呢?
首先ListView通過setAdapter方法,將Adapter與ListView關聯起來,查看ListView的setAdater方法源碼可知,setAdater將傳遞經來的Adapter的引用複製給了內部全局變量mAdapter,ListView在繪製每行的時候根據行號position調用父類AbsListView中的obtainView,obtainView首先會從recycler中獲取是否有匹配的視圖,如果存在的話,則調用adapter.getView方法,並傳遞了scrapView給convertView變量,否則傳遞的是null。
疑問二:ListView中數據發生變更了,我們一般會調用Adapter的 notifyDataSetChanged()方法, 那麼視圖是怎麼發生變化的呢?
ListView 中的數據適配器Adapter 採用的是觀察者模式
ListView在setAdapter時,會新建ApdateDataSetObserver,並註冊此觀察者。
AdapterDataSetObserver類實在AbsListView中定義:
AdapterView中AdapterDataSetObserver的實現如下(部分省略):
AdapterDataSetObserver 實現了DataSetObserver接口,並重寫了onChanger方法,裏面調用了requestLayout方法,此方法的作用是要求parent view 重新調用它的哦弄Measure onLayout方法重新佈局視圖,但不會重新繪製任何視圖包括該調用者本身。
requestLayout的實現方法需要到View類中查看:可參考:http://blog.csdn.net/androiddevelop/article/details/8561076
疑問三:RecyclerBin的數據結構是這樣的呢?當ListView有多個視圖類型(在界面上就是有不同的樣式和數據類型)又是怎麼選擇合適的convertView的呢?
首先看一下RecycleBin的類定義(AbsListView中內部類)
從註釋中我們就可以得知 :
RecycleBin一共有兩個存儲結構分別是ActiveViews 和 ScrapViews
ActiveViews儲存當前在界面(手機顯示區域)中顯示View,移出界面的view會存入ScrapViews
ScrapViews存儲當前已經滑動出當前界面(手機顯示區域)顯示的View,這些view存儲起來相當於回收,當再次請求的時候從此存儲中取出反覆使用。
當ListView中有N個視圖類型時,RecycleBin會創建N個scrapView數組,每個類型一個view數組,後面在獲取view時會先判斷view的類型,然後到對應的數組中去取。
怎麼從ScrapViews中獲取可用的view視圖呢?getScrapView → retrieveFromScrap
retrieveFromScrap(這個不屬於RecycleBin類,是屬於外部類AbslistView中的方法)
根據position,從mScrapView中找:
1. 如果有view.scrappedFromPosition = position的,直接返回該view;
2. 否則返回mScrapView中最後一個;
3. 如果緩存中沒有view,則返回null;
下面,我們來分析下這三種情況在什麼條件下滿足?
a. 第三種情況,這個最簡單:
一開始,listview穩定後,顯示N個,此時mScrapView中是沒有緩存view的,當我們向上滾動一小段距離(第一個此時仍顯示部分),新的view將會顯示,此時listview會調用Adapter.getView,但是緩存中沒有,因此convertView是null,所以,我們得分配一塊內存來創建新的convertView;
b. 第二種情況:
在a中,我們繼續向上滾動,直接第一個view完全移出屏幕(假設沒有新的item),此時,第一個view就會被detach,並被加入到mScrapView中;然後,我們還繼續向上滾動,直接後面又將要顯示新的item view時,此時,系統會從mScrapView中找position對應的View,顯然,是找不到的,則將從mScrapView中,取最後一個緩存的view傳遞給convertView;
c. 第一種情況:
緊接着在b中(標示爲橙色的文字後面),第一個被完全移出,加入到mScrapView中,且沒有新增的item到listview中,此時,緩存中就只有第一個view;然後,我此時向下滑動,則之前的第一個item,將被顯示出來,此時,從緩存中查找position對應的view有沒有,當然,肯定是找到了,就直接返回了。