一、什么是LayoutInflater?
翻譯源碼中的解釋:
實例化一個布局XML文件到他相應的View視圖中。他從未被直接使用。相反,需要使用Activiy中的getLayoutInflater()或Context的getSystemService()來檢索一個標準的LayoutInflater實例,該實例已經(jīng)連接到當前的上下文并正確配置為您正在運行的設(shè)備;
要為您的視圖創(chuàng)建一個新的LayoutInflater,需要使用額外的Factory,你可以使用 cloneInContext 克隆現(xiàn)有的ViewFactory,然后在其上調(diào)用 setFactory()來包含您的Factory。
出于性能遠遠,視圖填充在很大程度上依賴于在構(gòu)建時完成的XML文件的預處理。因此,目前不可能在運行時通過XmlPullParser在普通的XML文件上使 LayoutInflater,它只對編譯后的資源(R.)返回的XmlPullParse有效
通過上面的翻譯,我自認為的LayoutInflater簡單理解如下:
- LayoutInflater是一個視圖填充器;
- 獲取LayoutInflater 需要通過 Activity.getLayoutInflater() 或 Context.getSystemService();
- 可以自定義一個新的LayoutInflater(目前我還沒有遇到過,不理解);
- LayoutInflater只對構(gòu)建的XML的布局編譯后的R文件返回的XmlPullParse有效;
二、LayoutInflater的獲取
第一種方式: 從給定的上下文中獲取LayoutInflater:
LayoutInflater inflater = LayoutInflater.from(context);
第二種方式:在Activity中獲取LayoutInflater:
LayoutInflater inflater = getLayoutInflater();
第三種方式:通過context.getSystemService()獲?。?/p>
LayoutInflater inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
源碼調(diào)用分析
第一種 LayoutInflater.from(context);調(diào)用方式所用源碼:
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
.....
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
.....
}
第二中Activity中 getLayoutInflater()調(diào)用源碼分析:
Activity.class的源代碼:
public class Activity extends ....... {
.........
@NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
.........
@NonNull
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
.........
final void attach(.....){
......
mWindow = new PhoneWindow(this, window, activityConfigCallback);
.......
}
.........
}
PhoneWindow源碼:
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
查看源碼可以發(fā)現(xiàn):第一種調(diào)用的是第三種實例,第二種調(diào)用的是第一種實例,所以可以得到結(jié)果是:最終獲取LayoutInflater都是通過第三種方式獲取滴;
三、獲取View : View.inflate()和LayoutInflate.inflater()
在項目中,經(jīng)常需要用到將一個.xml布局文件轉(zhuǎn)為View,這里有兩種方式:
1、使用View的靜態(tài)方法inflate,不用獲取LayoutInflater,其實是源碼內(nèi)部同樣獲取了LayoutInflater:
View中的源碼:
/**
* 從xml資源中填充視圖,這個方法封裝了LayoutInflater
* @param context 您的Activity或Application上下文對象
* @param resouce 需要填充的資源ID
* @param root 一個視圖組,他將是父視圖組,用于正確填充layout的參數(shù)
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
2、使用LayoutInflater.inflater():
有四種調(diào)用方式:
mLayoutInflater.inflate(@LayoutRes int resource,@Nullable ViewGroup root);
mLayoutInflater.inflate(XmlPullParser parser,@Nullable ViewGroup root);
mLayoutInflater.inflate(@Layoutres int resourse,@Nullable ViewGroup root ,boolean attachToRoot);
mLayoutInflater.inflater(XmlPullParser parser,@Nullable ViewGroup root,boolean attachToToot) ;
查看上面四種方法調(diào)用的源碼后,發(fā)現(xiàn):最終調(diào)用的都是第四種方法;
在實際項目開放中,經(jīng)常使用的是一下兩種方法:
mLayoutInflater.inflate(@LayoutRes int resource,@Nullable ViewGroup root);
mLayoutInflater.inflate(@Layoutres int resourse,@Nullable ViewGroup root ,boolean attachToRoot);
可以看見,一個是兩個參數(shù),一個是三個參數(shù),而兩個參數(shù)的inflate,其實也是調(diào)用的三個參數(shù)的inflate,現(xiàn)在來看看三個參數(shù)代表的意義和使用需要注意的事項:
@LayoutRes int resource : 這個很明顯,是需要傳入的布局的資源ID;
@Nullable ViewGroup root : 需要附加到resource資源文件的父控件,即調(diào)用inflate方法會得到一個View對象,root參數(shù)就是接受該對象的容器;
boolean attachToRoot : 是否把inflate得到的View對象添加到root中,該參數(shù)為false時,表示不直接添加到root,true時,表示直接添加到root中;
三個參數(shù)都明白意思了,那現(xiàn)在再把使用的幾種參數(shù)情況理解一下:
既然兩個參數(shù)最終都是調(diào)用的三個參數(shù)方法,那先分析三個參數(shù)的inflate方法:
現(xiàn)在定義出來需要用到的布局文件:
Activity布局文件,Activity的xml布局文件activity_main:里面只有一個空了Linearlayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/ll_root">
</LinearLayout>
再定義一個用來填充的布局文件inflatelayout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="測試按鈕"/>
</LinearLayout>
1、root不為空,attachToRoot為true:
在代碼中inflatelayout.xml添加到我的Activity布局中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ; //Activity的根布局
getLayoutInflater().inflate(R.layout.inflatelayout,ll_root,true); //進行布局填充
}
查看項目運行結(jié)果:
可以看見,已經(jīng)將填充的布局添加到activty中了,這是因為第三個參數(shù)為true,表示將第一個參數(shù)所指的的布局添加到第二個參數(shù)的View中;
2、root不為空,attachToRoot為false:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ;
View view = getLayoutInflater().inflate(R.layout.inflatelayout,ll_root,false);
ll_root.addView(view);
}
上面代碼實現(xiàn)了與attachToRoot為true一樣的運行效果,現(xiàn)在分析一下,為false的情況:
attachToRoot為false,表示不將第一個參數(shù)所指定的布局文件添加到第二個參數(shù)的View中;在這里會有一個疑問:既然不將布局文件添加到指定的root中,那為什么不直接將第二個參數(shù)給null?這里就涉及另外一個問題:我們給控件所指定的layout_weight和layout_height到底是什么意思?該屬性表示一個控件在容器中大小,就是說這個控件必須在容器中,這個屬性才有意義,否則無意義!那由此我們可以等到這個結(jié)論:如果我直接將inflatelayout.xml填充而不給他指定一個父布局,則inflatelayout.xml的根節(jié)點的高寬會失效,如果想讓其有效,就必須為其指定一個父容器。現(xiàn)在我們能明白root和attachToRoot在這里器到的作用:設(shè)置root不為空,是為了讓inflatelayout.xml的根布局寬高有效,attachToRoot就是不自動將inflateLayout.xml填充到父容器中。
3、root為空:
其實滴二種情況也將root為空的給解釋一下。這里在重復一下:root不為空,則填充的xml文件的根布局的寬高設(shè)置有效,attachToRoot決定是否將填充布局自動添加到root中; root為空,即不為填充控件指定父布局,這個時候attachToRoot無論是true還是false都是沒有意義的!
看下面代碼的例子:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout ll_root = (LinearLayout)findViewById(R.id.ll_root) ;
View view = getLayoutInflater().inflate(R.layout.inflatelayout,null,false);
// View view = getLayoutInflater().inflate(R.layout.inflatelayout,null,true);
ll_root.addView(view);
}
root為空,attachToRoot無論為true還是false,運行結(jié)果都為下圖:
因為inflate時沒有指定容器,所以他個寬高失效了,但如果我設(shè)置button的寬高,則會有效,因為他是處于一個容器中的;
現(xiàn)在分析兩個參數(shù)的情況
我們先看兩個參數(shù)的調(diào)用源碼:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
可以很明顯看出,兩個參數(shù)的inflate實際上調(diào)用的也是三個參數(shù)的inflate方法,attchToRoot根據(jù)root來判斷值,所以最終的結(jié)果也就分為以下兩種情況:
- root為null,等同于三個參數(shù)的:root為空,attachToRoot為false;
- root不為null,等同于三個參數(shù)的:root不為空,attachToRoot為true;
上面就是LayoutInflater的簡單理解和inflate方法的使用介紹;
這里再記錄分析一個問題:為什么Activity布局的根節(jié)點的寬高屬性會生效?
大部分Activity頁面都由兩部分組成(Android版本號和應用主題會影響到Activity頁面的組成,這里以常見頁面為例),我們的Activity頁面中有一個頂級View叫DecorView,DecorView中包含一個Vertical的LinearLayout,Linearlayout由兩部分組成,第一部分為標題欄,第二部分為內(nèi)容欄,內(nèi)容欄是一個FrameLayout,我們在Activity中調(diào)用setContent就是將View添加到這個FrameLayout中;
看Activity中的setContent源碼:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
可以看見再設(shè)置的時候,獲取了android.R.id.content的View,并將其做為了父控件后使用的 LayoutInflater.from(mContext).inflate(resId, contentParent);所以我們設(shè)置的布局的寬高會生效;
本文參考鏈接:三個案例帶你看懂LayoutInflater中inflate方法兩個參數(shù)和三個參數(shù)的區(qū)別