Understanding Android's LayoutInflater.inflate()

理解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)典運用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容