安卓5.1原始碼解析 : ListView解析 從繪製,重新整理機制到Item的回收機制全面講解
最近一直在研究關於安卓中常用控制元件的原始碼實現,也參考了不少文章,希望通過自己的總結加深一下記憶,我會從一個view的繪製流程去分析這個控制元件
作為安卓中最常用的控制元件ListView,我覺很很有必要學習一下Google的大牛是如何實現這種比較複雜的控制元件,包括ListVIew的繪製流程,ListView的快取機制,以及封裝思想,對今後自己能早出更好的輪子有所幫助.
注 : 所有的原始碼都是來自安卓5.1版本.
本文將從以下角度對安卓中最常用的控制元件ListView進行分析
ListView的構造
我們先從一個類的最開始構造方法開始研究,第二行,ListView在初始化的時候,先執行了super(context, attrs, defStyleAttr, defStyleRes)
方法,ListView的父類是AbsListView
,所以我們先看下父類的初始化究竟做了什麼
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- ListView 父類 AbsListView的構造
父類方法中呼叫了initAbsListView
進行ListView的初始化配置,之後就是拿到一些自定義屬性
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
initAbsListView()
這個方法中給ListView設定了一些初始化狀態
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 回到ListView的構造方法
可以看到在初始化狀態之後,通過a.getDrawable(com.Android.internal.R.styleable.ListView_divider);
拿到了分割線的樣式,這就是是我們通過在style檔案中復ListView_divider
可以自定義Item分割線的原因.而且還可以通過複寫ListView_overScrollHeader
,ListView_overScrollFooter
設定頭部和底部的drawble檔案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
最後總結一下,ListView在構造方法中,就是初始化了一些狀態,並且將分割線等樣式新增了進來,這就是我們可以通過在sylte.xml複寫對應的樣式達到修改分割線的原因.
onMeasure方法
在onMeasure方法中會根據我們自定義繼承BaseAdapter的adpter.getCount
方法拿到所有item的數量,並且通過View
child = obtainView(0, mIsScrap);
方法建立view,那麼這個view是怎麼建立的呢,進去看一下
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- obtainView
可以看到最終也是呼叫了mAdapter.getView(position, scrapView, this);
建立child,getView中的引數scrapView 就是被回收的view物件,後面會講到
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
總結一下.在onMeasure方法中,會通過我們設定進來的mAdpter的getCount方法拿到item的數量,通過getView的方法拿到我們建立的每一個view,當然ListVIew第一次建立的時候並沒有mAdapter的存在,只有在setAdapter被我們呼叫過後才會執行這些方法,也就是說在setAdapter中一定會呼叫requestLayout
方法重新走一遍流程,這個下面會進行講解.
onLayout方法
通過搜尋發現ListView中並沒有onLayout方法,那也就是說一定是在他的父類AbsListView
中,我們可以看到它呼叫了layoutChildren()
,從方法名看應該是對子view進行佈局,這個layoutChildren是一個空實現方法,也就是說應該是通過AbsListView的子類ListVIew
和GridView
進行實現
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- listView.layoutChildren()
這個方法比較長,我們具體看重點,這個方法中會判斷是否通過adapter進行新增資料的操作,並通過fillXXX()
方法進行對ItemView的填充,並且有兩個很重要的物件:
1.View[] mActiveViews
:存放的是當前ListView可以使用的待啟用的子item view
2.ArrayList<View>[] mScrapViews
:存放的是在ListView滑動過程中滑出螢幕來回收以便下次利用的子item view
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
總結一下,通過onLayout方法,就將item填充到了ListView中
Item的填充與Item的佈局
我們剛才講到,在layoutChidren中有幾個以fill開頭的方法就是具體的Item的填充方法,
- fillSpecific()
這個方法中會根據mStackFromBottom引數判斷填充方向,通過fillUp
,fillDown
進行填充
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- fillDown
以fillDown()
舉例
第一次進入nextTop就是padding,也就是最頂部的位置,通過一個while迴圈,只要nextTop沒有超出end(ListView內容高度)就一直makeAndAddView()
建立view,nextTop在迴圈裡會根據Item數量進行迴圈賦值,只要判斷當前這個item的nextTop超出listView,就停止這個迴圈,通過這種方法就將可見view都填充出來了
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected)
listView就是通過這個方法呼叫obtainView(position, mIsScrap)
mAdapter.getView建立view,然後通過setupChild(child,
position, y, flow, childrenLeft, selected, mIsScrap[0]);
這個方法進行對子view的佈局,記住這些方法都在while迴圈中,
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
boolean selected, boolean recycled)
在這個方法中,通過拿到上面while迴圈傳經來的引數,呼叫了子child的measure和layout方法進行測量和繪製,到此listView中可見區域的view就被填充出來了.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
上面就是ListView中item的填充,下面我們來看看setAdapter中究竟做了什麼操作
setAdapter
setAdapter中通過mAdapter.registerDataSetObserver(mDataSetObserver);
註冊一個AdapterDataSetObserver
訂閱者,每當呼叫notifyDataSetChange
的時候,就會觸發AdapterDataSetObserver
的onChanged
的方法,這個是觀察者模式,不懂得可以參考下其他文章,這裡就不多做贅述,這個方法最終呼叫requestLayout方法,也就是說我們每次setAdapter之後就會重新佈局,這時候mAdapter不為空,就會走剛才所說的繪製流程.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
notifyDataSetChanged
這個方法在BaseAdapter
中
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
這時候根據觀察者模式,會呼叫訂閱者AdapterDataSetObserver
的onChanged方法,上面提到過,最終還是會呼叫requestLayout進行重新佈局
- onChanged
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
ListView的回收機制
最後我們來看看ListView的複用機制
要想了解這方面,先要從ListView滑動開始看,滾動核心方法AbsListView
的trackMotionScroll
,在這個方法中實現了對ListView,Item的快取
- trackMotionScroll
第一步 : 這個方法會先判斷我們先在滑動的位置是否已經到最頂部,或者最底部,如果到了邊界值,就不能再滑動了
第二步 : 拿手指向上移動,也就是下滑狀態來說名:先遍歷所有的item,如果發現這個item的底部還在可視範圍之內,說明這個item還沒有銷燬,如果超出,則表示需要被快取起來,也就是會加入到mRecycler的mScrapViews(超出螢幕的集合)中儲存,這個集合之前有說過,專門用來儲存超出螢幕的Item.並將劃出的view通過detachViewsFromParent
從ListView中detach掉
第三步 : 判斷是否有Item滾入了ListView中,如果滾入,呼叫fillGap
方法進行填充,這個方法中會呼叫之前說過的fillDown
或者fillUp
方法填充item,並新增到mActivated
(當前螢幕中Item的集合)中,這樣就實現了Item的快取.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
以上就是對ListView繪製流程以及其中的觀察者模式等等,後面我也會對ReyclerView 以及ViewPager進行原始碼分析,希望通過這種方式,更加深刻的瞭解各種View的實現,對以後自定義控制元件的編寫提供更好的思想.
轉自:http://blog.csdn.net/hfyd_/article/details/53768690?_t_t_t=0.8988156646955758
相關文章
- Android 9.0 原始碼_機制篇 -- 全面解析 HandlerAndroid原始碼
- Qt原始碼解析之-從PIMPL機制到d指標QT原始碼指標
- RecyclerView 原始碼深入解析——繪製流程、快取機制、動畫等View原始碼快取動畫
- [原始碼解析] 從TimeoutException看Flink的心跳機制原始碼Exception
- Android AccessibilityService機制原始碼解析Android原始碼
- 從原始碼的角度解析Mybatis的會話機制原始碼MyBatis會話
- Android全面解析之Window機制Android
- Android全面解析之Context機制AndroidContext
- Dubbo原始碼解析之SPI機制原始碼
- langchain chatchat執行機制原始碼解析LangChain原始碼
- Spring事件監聽機制原始碼解析Spring事件原始碼
- Spark Shuffle機制詳細原始碼解析Spark原始碼
- Volley 原始碼解析之快取機制原始碼快取
- JDK原始碼解析之Java SPI機制JDK原始碼Java
- 從原始碼解析 Go 的切片型別以及擴容機制原始碼Go型別
- 從原始碼全面剖析 React 元件更新機制原始碼React元件
- 解碼注意力Attention機制:從技術解析到PyTorch實戰PyTorch
- Spring 原始碼解析一:SpringMVC 的載入機制原始碼SpringMVC
- 從零開始仿寫一個抖音App——Android繪製機制以及Surface家族原始碼全解析APPAndroid原始碼
- 基於原始碼分析 Android View 繪製機制原始碼AndroidView
- Python記憶體管理機制-《原始碼解析》Python記憶體原始碼
- 原始碼深度解析 Handler 機制及應用原始碼
- React-原始碼解析-setState執行機制React原始碼
- 深入js基礎:從記憶體機制、解析機制到執行機制(長文預警)JS記憶體
- Handler機制解析
- Netty原始碼解析 -- 零拷貝機制與ByteBufNetty原始碼
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- Handler訊息處理機制原始碼解析 上原始碼
- 從Windows 到安卓:多重攻擊機制的遠控的分析Windows安卓
- SPDK QOS機制解析
- ETCD核心機制解析
- 通過WordCount解析Spark RDD內部原始碼機制Spark原始碼
- Netty原始碼解析 -- 事件迴圈機制實現原理Netty原始碼事件
- Netty原始碼解析 -- ChannelPipeline機制與讀寫過程Netty原始碼
- 聊聊Dubbo – Dubbo可擴充套件機制原始碼解析套件原始碼
- Android原始碼解析之一 非同步訊息機制Android原始碼非同步
- Android訊息機制全面解析(Handler,MessageQueue,Looper,Threadlocal)AndroidOOPthread
- 全面剖析Android訊息機制原始碼Android原始碼
- 從閉包函式的變數自增的角度 – 解析js垃圾回收機制函式變數JS