Layout inflation在Android上下文環境下轉換XML文件成View結構對象的時候須要用到。java
LayoutInflater這個對象在Android的SDK中很常見,可是你絕對沒想到居然可以找到一個使用誤區。說不定你的App裏就是這麼用的!若是你在寫APP的時候像以下代碼同樣使用LayoutInflater的話:android
inflater.inflate(R.layout.my_layout, null);
請你繼續讀完這篇文章,稍後我會解釋爲何這樣作不對。框架
首先看一下LayoutInflater的工做原理,有兩個重載的版本可使用:函數
inflate(int resource, ViewGroup root)
和 inflate(int resource, ViewGroup root, boolean attachToRoot)
佈局
第一個參數指出要載入的佈局文件資源,第二個參數指出視圖結構中載入的佈局將要放入的根視圖。若是有第三個參數,那麼它用來決定是否把載入後的視圖綁定到給出的根視圖中。ui
最後兩個參數可能會致使一些問題。若是使用兩個參數的版本,Layoutinflater會自動嘗試把載入的視圖綁定到給定的根視圖對象中。可是,若是你傳遞null
,系統就不會嘗試綁定操做了,不然應用程序就崩潰了。spa
不少開發者會這樣作,認爲傳遞null
做爲根視圖就能夠禁用綁定操做了。不少時候不少開發者甚至不知道還有三個參數的Layoutinflater版本的存在,若是這麼作的話,也會同時禁用了根視圖的一個很重要的函數……可是以前我沒有研究過。code
如今咱們來仔細看看Android框架關於動態載入佈局的場景。orm
Adapter是最經常使用的場景,咱們常常須要使用LayoutInflater
來自定義ListView
(經過重寫getView()
方法),具體的方法簽名是這樣的:xml
getView(int position, View convertView, ViewGroup parent)
Fragment也會用到inflation操做,經過onCreateView()
方法建立view的時候會用到。這個方法的簽名是這樣的:
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
不知你有沒有注意到這一點,每次Framework須要你去載入一個佈局文件時,都會傳入一個ViewGroup參數(最後須要綁定到的根視圖),若是Layoutinflater設爲自動綁定到根視圖的話,會拋出一個異常。
因此你想一想看,若是我作綁定操做的話,爲何要給你一個ViewGroup參數呢?事實證實父視圖在這個inflation操做過程當中是很重要的,它會計算被載入的XML在根元素中的LayoutParams,若是傳入null
話,就等因而告訴框架「我不知道載入的View要放到哪一個父視圖中」。
問題在於,android:layout_xxx屬性會在父視圖對象中被從新計算,結果就是全部你定義的LayoutParams都會被忽略掉(由於沒有已知的父視圖對象)。而後你就納悶「爲何框架會忽略掉我本身定義的佈局屬性呢?仍是去StackOverFlow上看看,提一個bug吧」。
若是沒有設置LayoutParams,那麼最終ViewGroup也會給你生成一個默認的屬性,幸運的話(不少時候),這些默認的設置正好和你在XML文件中定義的同樣……因此你就察覺不到其實已經出現問題了。
你敢說你沒有在應用中碰到過這樣的場景嗎?看看下面的代碼,爲Listview
簡單地載入一個佈局文件:
R.layout.item_row
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="15dp" android:text="Text1" /> <TextView android:id="@+id/text2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Text2" /> </LinearLayout>
這裏咱們想把高度設置爲固定高度,上面把它設爲當前主題下的推薦高度……看似很合理。
可是,當咱們這樣載入佈局文件的時候,就不對了
public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflate(R.layout.item_row, null); } return convertView; }
而後結果就變成這樣了:
爲何設定的固定高度不起做用?這是由於你沒有把全部子View的高都設爲固定高度,只須要把根視圖的高設置成wrap_content就能夠了。不須要知道爲何會這樣(你能夠吐槽一下Google爲何這麼處理!)。
而若是這樣載入佈局的話就沒有問題:
public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflate(R.layout.item_row, parent, false); } return convertView; }
這樣咱們就獲得了想要的結果:
固然,也有須要在載入佈局的時候指定null
做爲父佈局對象,但這種狀況很是少。一個典型的例子就是爲AlertDialog中載入一個自定義佈局。看看下面的例子,使用和上面同樣的XML佈局文件來做爲對話框的佈局:
AlertDialog.Builder builder = new AlertDialog.Builder(context); View content = LayoutInflater.from(context).inflate(R.layout.item_row, null); builder.setTitle("My Dialog"); builder.setView(content); builder.setPositiveButton("OK", null); builder.show();
這裏的問題就是,AlertDialog.Builder
支持自定義佈局,可是setView()
方法不提供帶有佈局文件做爲參數的版本,因此只能先手動載入XML佈局文件。因爲最終會進入到對話框裏面,不會接觸到根佈局(事實上這時候尚未根佈局),因此咱們也操做不了佈局文件的最終父視圖對象,固然也就不能用於載入使用了。事實證實,這些都是可有可無的,由於AlertDialog
會擦除佈局上的全部Layoutparams
而後替換爲match_parent
。
因此,下次使用inflate()
函數時,若是還想輸入null
應該停下來想想「我真的不知道它該放到哪裏嗎?」
最後,你應該想一想兩個參數的inflate()
版本做爲一個便捷的使用方式,能夠忽略第三個參數(默認爲true
),可是不要想着爲了方便而傳遞一個null
卻忽略了第三個參數會默認是false
。