一、LayoutInflater
參考
Android LayoutInflater詳解
LayoutInflate的使用
在實際開發中LayoutInflater這個類還是非常有用的,它的作用類似于findViewById()。不同點是LayoutInflater是用來找res/layout/下的xml布局文件,并且實例化;而findViewById()是找xml布局文件下的具體widget控件(如Button、TextView等)。
具體作用:
1、對于一個沒有被載入或者想要動態載入的界面,都需要使用LayoutInflater.inflate()來載入;
2、對于一個已經載入的界面,就可以使用Activiyt.findViewById()方法來獲得其中的界面元素。
LayoutInflater作用是將layout的xml布局文件實例化為View類對象。
setContentView()一旦調用, layout就會立刻顯示UI;而inflate只會把Layout形成一個以view類實現成的對象,有需要時再用setContentView(view)顯示出來。一般在activity中通過setContentView()將界面顯示出來,但是如果在非activity中如何對控件布局設置操作了,這就需要LayoutInflater動態加載。
<pre>
//setContentView(R.layout.main);
LayoutInflater inflate = LayoutInflater.from(this);
View view = inflate.inflate(R.layout.main,null);
setContentView(view);
</pre>
LayoutInflater在Android中是“擴展”的意思,作用類似于findViewById(),不同的是LayoutInflater是用來獲得布局文件對象的,而findViewById()是用來獲得具體控件的。LayoutInflater經常在BaseAdapter的getView方法中用到,用來獲取整個View并返回。
LayoutInflater 是一個抽象類,在文檔中如下聲明:
public abstract class LayoutInflater extends Object
獲得 LayoutInflater 實例的三種方式:
- LayoutInflater inflater = getLayoutInflater();//調用Activity的getLayoutInflater()
- LayoutInflater inflater = LayoutInflater.from(context);
- LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
其實,這三種方式本質是相同的,從源碼中可以看出這三種方式最終本質是都是調用的Context.getSystemService()
二、inflater
現在我們來仔細看看Android框架關于動態載入布局的場景。
1.Adapter是最常用的場景,我們經常需要使用LayoutInflater來自定義ListView(通過重寫getView()方法),具體的方法簽名是這樣的:
getView(int position, View convertView, ViewGroup parent)
2.Fragment也會用到inflation操作,通過onCreateView()方法創建view的時候會用到。這個方法的簽名是這樣的:
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
不知你有沒有注意到這一點,每次Framework需要你去載入一個布局文件時,都會傳入一個ViewGroup參數(最后需要綁定到的根視圖)所以你想想看,如果我做綁定操作的話,為什么要給你一個ViewGroup參數呢?事實證明父視圖在這個inflation操作過程中是很重要的,它會計算被載入的XML在根元素中的LayoutParams,如果傳入null話,就等于是告訴框架“我不知道載入的View要放到哪個父視圖中”。
問題在于,android:layout_xxx屬性會在父視圖對象中被重新計算,結果就是所有你定義的LayoutParams都會被忽略掉(因為沒有已知的父視圖對象)。然后你就納悶“為什么框架會忽略掉我自己定義的布局屬性呢?還是去StackOverFlow上看看,提一個bug吧”。
如果沒有設置LayoutParams,那么最終ViewGroup也會給你生成一個默認的屬性,幸運的話(很多時候),這些默認的設置正好和你在XML文件中定義的一樣……所以你就察覺不到其實已經出現問題了。
以下參考Android LayoutInflater深度解析 給你帶來全新的認識
ListView的Item的布局文件:
<pre>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/id_btn"
android:layout_width="120dp"
android:layout_height="120dp" >
</Button>
</pre>
ListView的適配器:
<pre>
public View getView(int position, View convertView, ViewGroup parent)
{
ViewHolder holder = null;
if (convertView == null)
{
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
// convertView = mInflater.inflate(R.layout.item, parent ,false);
// convertView = mInflater.inflate(R.layout.item, parent ,true);
holder.mBtn = (Button) convertView.findViewById(R.id.id_btn);
convertView.setTag(holder);
} else
{
holder = (ViewHolder) convertView.getTag();
}
holder.mBtn.setText(mDatas.get(position));
return convertView;
}
</pre>
圖3直接報錯
<pre>
FATAL EXCEPTION: main
java.lang.UnsupportedOperationException:
addView(View, LayoutParams) is not supported in AdapterView
</pre>
由上面getView三行代碼的變化,產生3個不同的結果,可以看到
inflater(resId, null )的確不能正確處理寬高的值,但是inflater(resId,parent,false)并非和inflater(resId, null )效果一致,它可以看出完美的顯示了寬和高。而inflater(resId,parent,true)報錯了
分析源碼已經可以看出:
Inflate(resId , null ) 只創建temp ,返回temp
Inflate(resId , parent, false )創建temp,然后執行temp.setLayoutParams(params);返回temp
Inflate(resId , parent, true ) 創建temp,然后執行root.addView(temp, params);最后返回root
由上面已經能夠解釋:
Inflate(resId , null )不能正確處理寬和高是因為:layout_width,layout_height是相對了父級設置的,必須與父級LayoutParams一致。而此temp的getLayoutParams為null
Inflate(resId , parent,false ) 可以正確處理,因為temp.setLayoutParams(params);這個params正是root.generateLayoutParams(attrs);得到的。
Inflate(resId , parent,true )不僅能夠正確的處理,而且已經把resId這個view加入到了parent,并且返回的是parent,和以上兩者返回值有絕對的區別。
圖3中的報錯是因為源碼中調用了root.addView(temp, params);而此時的root是我們的ListView,ListView為AdapterView的子類:直接看AdapterView的源碼:
<pre>
@Override
public void addView(View child) {
throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
}
</pre>
可以看到這個錯誤為啥產生了。