理解Android的LayoutInflater.inflate()方法
[Android 官方文檔](http://developer.android.com/reference/android/view/LayoutInflater.html#inflate(int, android.view.ViewGroup, boolean))
BigNerdranch 博客
注:相對于官方博客,BigNerdranch上的博客提供了更接地氣的通俗易懂的解釋。
下文為汲取了諸多前輩的經(jīng)驗知識后個人總結(jié)所得,如有理解或解釋錯誤的地方,請諸位看客老爺指正!
inflate意思是膨脹,打氣。從XML到View,原本的code到可視化的view,就是一個充氣、具象化的過程。所以我理解為具象化。
LayoutInflater 的作用是 將一個XML布局文件實例化到其對應(yīng)的View對象中。它不能直接用,需要檢索當(dāng)前的上下文來獲取配置,從而實例化。
LayoutInflater.inflate:
View inflate (int resource, ViewGroup root, boolean attachToRoot)
| 參數(shù)名 | 解釋 |
|---|---|
| resource | int,要加載的XML布局文件ID |
| root | ViewGroup,可選項(取決于第三個參數(shù)),作為要加載的布局文件的父節(jié)點 |
| attachToRoot | boolean,是否將新具象化的層次結(jié)構(gòu)附加到根參數(shù) |
官方文檔給的解釋到此為止了,那么這個attachToRoot到底做了什么?
通俗解釋
如果attachToRoot被設(shè)為true,那么第一個參數(shù)中被指定的布局會被附加到第二個參數(shù)中指定的ViewGroup中。
然后該方法返回它們組合形成的View,并將第二個參數(shù)ViewGroup作為根節(jié)點。
如果attachToRoot被設(shè)為false,那么第一個參數(shù)中指定的布局被具象化,并作為一個View返回。
這個意思是,之后這個View會以其他某種方式加入到ViewGroup中。
在以上兩種情況下,我們都需要ViewGroup的布局參數(shù),來確定我們第一個參數(shù)具象化的View的size和它的位置。
設(shè) attachToRoot 為 true
假設(shè)我們在一個XML布局文件 custom_button 中定義了一個Button,并設(shè)置了它的寬高為match_parent。
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/custom_button"/>
現(xiàn)在,我們通過代碼方式把這個Button添加到一個Fragment或者Activity的線性布局里。假設(shè)這個LinearLayout已經(jīng)是一個
成員變量,記為 mLinearLayout,我們這樣添加Button:
inflater.inflate(R.layout.custom_button, mLinearLayout, true);
我們首先指明我們想要從這個button的布局文件中具象化它;之后我們告訴inflater,我們想把這個button添加到mLinearLayout中。那布局參數(shù)就很高興,因為知道了button已經(jīng)被添加到一個線性布局文件中了。那這Button的布局參數(shù)就應(yīng)該是LinearLayout.LayoutParams。
另一種傳遞true值給attachToRoot的情況是使用定制View。看一個例子。在一個布局文件中用<merge>標(biāo)簽作為它的根節(jié)點。
這表示這個布局文件 允許 根據(jù)它可能具有的根ViewGroup的類型 來靈活安排。
public class MyCustomView extends LinearLayout {
...
private void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.view_with_merge_tag, this);
}
}
在這個例子中,布局文件沒有作為根的ViewGroup,所以我們指定了我們定制的LinearLayout作為它的根。
如果我們的布局文件有一個FrameLayout作為它的根而不是<merge>,那么這個FrameLayout和它的子節(jié)點就會正常具象化。然后這個FrameLayout和子節(jié)點會被加載到這個LinearLayout,使得LinearLayout成為根節(jié)點包含其它節(jié)點。
設(shè) attachToRoot 為 false
在這種情況下,在第一個參數(shù)中指定的View不會在這個時候被添加到第二個參數(shù)所指的ViewGroup。
還是剛才的Button。當(dāng)我們傳false給attachToRoot的時候,依然可以把Button添加到mLinearLayout上————只要我們后面自己手動添加即可。
Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
mLinearLayout.addView(button);
上面這兩行代碼和之前的效果是相同的。傳入false后,我們不想第一時間把這具象化的view添加到第二個參數(shù)指定的ViewGroup。而是,之后某個時間添加。在這個例子中,某個時間就是在具象化后,調(diào)用addView()方法的時候。
上面的例子中,為了實現(xiàn)相同的效果,傳入false的做法比傳入true的做法要費事一點。那么為什么還這么做?有沒有一些情況下必須要這樣做的呢?答案是肯定的!有些情況下,必須這么做~
一個RecyclerView的子類在具象化的時候,attachToRoot必須傳入false。子視圖是在onCreateViewHolder()方法中具象化的:
public ViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent, int viewType){
View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
return new ViewHolder(view);
}
這里,RecyclerView(而不是我們)負(fù)責(zé)決定什么時候具象化并且展示它的子視圖。因此,任何時候attachToRoot參數(shù)就一定要是false,我們不負(fù)責(zé)添加它到ViewGroup。
當(dāng)我們在onCreateView()中具象化并且返回一個Fragment的視圖時,要確保傳false到attachToRoot中。如果我們傳入true,就會得到一個IllegalStateException,因為指定的孩子View已經(jīng)有父ViewGroup了。我們應(yīng)該做的是,指明這個Fragment的view將會放在我們的Activity的什么地方。這個,就是FragmentManager的工作了,它負(fù)責(zé)添加、移除和替換Fragments:
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);
if (fragment == null) {
fragment = new MainFragment();
fragmentManager.beginTransaction()
.add(R.id.root_viewGroup, fragment)
.commit();
}
root_viewGroup容器是我們指定給onCreateView()的第一個參數(shù),作為ViewGroup來容納我們的Fragment。同時,它也作為LayoutInflater.inflate()的第二個參數(shù)。FragmentManager會將Fragment的View添加到這個ViewGroup當(dāng)中,當(dāng)然,我們不希望添加它兩次,所以,傳遞給attachToRoot的值是false:
public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
…
return view;
}
如果我們不想在onCreateView()中將View附加給ViewGroup,那為什么我們還要將Fragment的父ViewGroup放在第一位置呢?又為什么inflate()方法要求有一個根ViewGroup呢?
這是因為,即使我們不想立刻將具象化的View添加到它的父ViewGroup中去,為了知道當(dāng)這個View最終被添加的時候,它的size和位置,我們也要需要父ViewGroup的布局參數(shù)LayoutParams。
最后一點建議,通常來說,當(dāng)我們可以添加父ViewGroup的時候,我們還是加上,不將其設(shè)置為null。
總結(jié)
希望這篇理解文章可以幫你在用LayoutInflater時避免程序崩潰、誤解或者不能掌控它的行為。
下面是在特定場合使用時的一些注意點:
- 當(dāng)我們有一個父ViewGroup可以傳入ViewGroup時,將它傳進(jìn)去。
- 盡量避免傳入null給root ViewGroup。
- 當(dāng)我們不需要負(fù)責(zé)將布局文件的視圖添加給它的root ViewGroup的時候,設(shè) attachToRoot 為 false。
- 不要給一個已經(jīng)被添加到ViewGroup的View傳入true。
- 定制視圖是傳入true值的經(jīng)典運用。