轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/12921889html
有段時間沒寫博客了,感受都有些生疏了呢。最近繁忙的工做終於告一段落,又有時間寫文章了,接下來還會繼續堅持每一週篇的節奏。java
有很多朋友跟我反應,都但願我能夠寫一篇關於View的文章,講一講View的工做原理以及自定義View的方法。沒錯,承諾過的文章我是必定要兌現的,並且在View這個話題上我還準備多寫幾篇,儘可能能將這個知識點講得透徹一些。那麼今天就從LayoutInflater開始講起吧。android
相信接觸Android久一點的朋友對於LayoutInflater必定不會陌生,都會知道它主要是用於加載佈局的。而剛接觸Android的朋友可能對LayoutInflater不怎麼熟悉,由於加載佈局的任務一般都是在Activity中調用setContentView()方法來完成的。其實setContentView()方法的內部也是使用LayoutInflater來加載佈局的,只不過這部分源碼是internal的,不太容易查看到。那麼今天咱們就來把LayoutInflater的工做流程仔細地剖析一遍,也許還能解決掉某些困擾你心頭多年的疑惑。微信
先來看一下LayoutInflater的基本用法吧,它的用法很是簡單,首先須要獲取到LayoutInflater的實例,有兩種方法能夠獲取到,第一種寫法以下:ide
固然,還有另一種寫法也能夠完成一樣的效果:佈局
其實第一種就是第二種的簡單寫法,只是Android給咱們作了一下封裝而已。獲得了LayoutInflater的實例以後就能夠調用它的inflate()方法來加載佈局了,以下所示:this
inflate()方法通常接收兩個參數,第一個參數就是要加載的佈局id,第二個參數是指給該佈局的外部再嵌套一層父佈局,若是不須要就直接傳null。這樣就成功成功建立了一個佈局的實例,以後再將它添加到指定的位置就能夠顯示出來了。spa
下面咱們就經過一個很是簡單的小例子,來更加直觀地看一下LayoutInflater的用法。好比說當前有一個項目,其中MainActivity對應的佈局文件叫作activity_main.xml,代碼以下所示:.net
這個佈局文件的內容很是簡單,只有一個空的LinearLayout,裏面什麼控件都沒有,所以界面上應該不會顯示任何東西。code
那麼接下來咱們再定義一個佈局文件,給它取名爲button_layout.xml,代碼以下所示:
這個佈局文件也很是簡單,只有一個Button按鈕而已。如今咱們要想辦法,如何經過LayoutInflater來將button_layout這個佈局添加到主佈局文件的LinearLayout中。根據剛剛介紹的用法,修改MainActivity中的代碼,以下所示:
能夠看到,這裏先是獲取到了LayoutInflater的實例,而後調用它的inflate()方法來加載button_layout這個佈局,最後調用LinearLayout的addView()方法將它添加到LinearLayout中。
如今能夠運行一下程序,結果以下圖所示:
Button在界面上顯示出來了!說明咱們確實是藉助LayoutInflater成功將button_layout這個佈局添加到LinearLayout中了。LayoutInflater技術普遍應用於須要動態添加View的時候,好比在ScrollView和ListView中,常常均可以看到LayoutInflater的身影。
固然,僅僅只是介紹瞭如何使用LayoutInflater顯然是遠遠沒法知足你們的求知慾的,知其然也要知其因此然,接下來咱們就從源碼的角度上看一看LayoutInflater究竟是如何工做的。
無論你是使用的哪一個inflate()方法的重載,最終都會展轉調用到LayoutInflater的以下代碼中:
從這裏咱們就能夠清楚地看出,LayoutInflater其實就是使用Android提供的pull解析方式來解析佈局文件的。不熟悉pull解析方式的朋友能夠網上搜一下,教程不少,我就不細講了,這裏咱們注意看下第23行,調用了createViewFromTag()這個方法,並把節點名和參數傳了進去。看到這個方法名,咱們就應該能猜到,它是用於根據節點名來建立View對象的。確實如此,在createViewFromTag()方法的內部又會去調用createView()方法,而後使用反射的方式建立出View的實例並返回。
固然,這裏只是建立出了一個根佈局的實例而已,接下來會在第31行調用rInflate()方法來循環遍歷這個根佈局下的子元素,代碼以下所示:
能夠看到,在第21行一樣是createViewFromTag()方法來建立View的實例,而後還會在第24行遞歸調用rInflate()方法來查找這個View下的子元素,每次遞歸完成後則將這個View添加到父佈局當中。
這樣的話,把整個佈局文件都解析完成後就造成了一個完整的DOM結構,最終會把最頂層的根佈局返回,至此inflate()過程所有結束。
比較細心的朋友也許會注意到,inflate()方法還有個接收三個參數的方法重載,結構以下:
那麼這第三個參數attachToRoot又是什麼意思呢?其實若是你仔細去閱讀上面的源碼應該能夠本身分析出答案,這裏我先將結論說一下吧,感興趣的朋友能夠再閱讀一下源碼,校驗個人結論是否正確。
1. 若是root爲null,attachToRoot將失去做用,設置任何值都沒有意義。
2. 若是root不爲null,attachToRoot設爲true,則會在加載的佈局文件的最外層再嵌套一層root佈局。
3. 若是root不爲null,attachToRoot設爲false,則root參數失去做用。
4. 在不設置attachToRoot參數的狀況下,若是root不爲null,attachToRoot參數默認爲true。
好了,如今對LayoutInflater的工做原理和流程也搞清楚了,你該知足了吧。額。。。。還嫌這個例子中的按鈕看起來有點小,想要調大一些?那簡單的呀,修改button_layout.xml中的代碼,以下所示:
這裏咱們將按鈕的寬度改爲300dp,高度改爲80dp,這樣夠大了吧?如今從新運行一下程序來觀察效果。咦?怎麼按鈕仍是原來的大小,沒有任何變化!是否是按鈕仍然不夠大,再改大一點呢?仍是沒有用!
其實這裏無論你將Button的layout_width和layout_height的值修改爲多少,都不會有任何效果的,由於這兩個值如今已經徹底失去了做用。平時咱們常用layout_width和layout_height來設置View的大小,而且一直都能正常工做,就好像這兩個屬性確實是用於設置View的大小的。而實際上則否則,它們實際上是用於設置View在佈局中的大小的,也就是說,首先View必須存在於一個佈局中,以後若是將layout_width設置成match_parent表示讓View的寬度填充滿布局,若是設置成wrap_content表示讓View的寬度恰好能夠包含其內容,若是設置成具體的數值則View的寬度會變成相應的數值。這也是爲何這兩個屬性叫做layout_width和layout_height,而不是width和height。
再來看一下咱們的button_layout.xml吧,很明顯Button這個控件目前不存在於任何佈局當中,因此layout_width和layout_height這兩個屬性理所固然沒有任何做用。那麼怎樣修改才能讓按鈕的大小改變呢?解決方法其實有不少種,最簡單的方式就是在Button的外面再嵌套一層佈局,以下所示:
能夠看到,這裏咱們又加入了一個RelativeLayout,此時的Button存在與RelativeLayout之中,layout_width和layout_height屬性也就有做用了。固然,處於最外層的RelativeLayout,它的layout_width和layout_height則會失去做用。如今從新運行一下程序,結果以下圖所示:
OK!按鈕的終於能夠變大了,這下總算是知足你們的要求了吧。
看到這裏,也許有些朋友心中會有一個巨大的疑惑。不對呀!平時在Activity中指定佈局文件的時候,最外層的那個佈局是能夠指定大小的呀,layout_width和layout_height都是有做用的。確實,這主要是由於,在setContentView()方法中,Android會自動在佈局文件的最外層再嵌套一個FrameLayout,因此layout_width和layout_height屬性纔會有效果。那麼咱們來證明一下吧,修改MainActivity中的代碼,以下所示:
能夠看到,這裏經過findViewById()方法,拿到了activity_main佈局中最外層的LinearLayout對象,而後調用它的getParent()方法獲取它的父佈局,再經過Log打印出來。如今從新運行一下程序,結果以下圖所示:
很是正確!LinearLayout的父佈局確實是一個FrameLayout,而這個FrameLayout就是由系統自動幫咱們添加上的。
說到這裏,雖然setContentView()方法你們都會用,但實際上Android界面顯示的原理要比咱們所看到的東西複雜得多。任何一個Activity中顯示的界面其實主要都由兩部分組成,標題欄和內容佈局。標題欄就是在不少界面頂部顯示的那部份內容,好比剛剛咱們的那個例子當中就有標題欄,能夠在代碼中控制讓它是否顯示。而內容佈局就是一個FrameLayout,這個佈局的id叫做content,咱們調用setContentView()方法時所傳入的佈局其實就是放到這個FrameLayout中的,這也是爲何這個方法名叫做setContentView(),而不是叫setView()。
最後再附上一張Activity窗口的組成圖吧,以便於你們更加直觀地理解:
好了,今天就講到這裏了,支持的、吐槽的、有疑問的、以及打醬油的路過朋友儘管留言吧 ^v^ 感興趣的朋友能夠繼續閱讀 Android視圖繪製流程徹底解析,帶你一步步深刻了解View(二) 。
第一時間得到博客更新提醒,以及更多技術信息分享,歡迎關注個人微信公衆號,掃一掃下方二維碼或搜索微信號guolin_blog,便可關注。