一、什么是LayoutInflater?
翻译源码中的解释:
实例化一个布局XML文件到他相应的View视图中。他从未被直接使用。相反,需要使用Activiy中的getLayoutInflater()或Context的getSystemService()来检索一个标准的LayoutInflater实例,该实例已经连接到当前的上下文并正确配置为您正在运行的设备;
要为您的视图创建一个新的LayoutInflater,需要使用额外的Factory,你可以使用 cloneInContext 克隆现有的ViewFactory,然后在其上调用 setFactory()来包含您的Factory。
出于性能远远,视图填充在很大程度上依赖于在构建时完成的XML文件的预处理。因此,目前不可能在运行时通过XmlPullParser在普通的XML文件上使 LayoutInflater,它只对编译后的资源(R.)返回的XmlPullParse有效
通过上面的翻译,我自认为的LayoutInflater简单理解如下:
- LayoutInflater是一个视图填充器;
- 获取LayoutInflater 需要通过 Activity.getLayoutInflater() 或 Context.getSystemService();
- 可以自定义一个新的LayoutInflater(目前我还没有遇到过,不理解);
- LayoutInflater只对构建的XML的布局编译后的R文件返回的XmlPullParse有效;
二、LayoutInflater的获取
第一种方式: 从给定的上下文中获取LayoutInflater:
LayoutInflater inflater = LayoutInflater.from(context);
第二种方式:在Activity中获取LayoutInflater:
LayoutInflater inflater = getLayoutInflater();
第三种方式:通过context.getSystemService()获取:
LayoutInflater inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
源码调用分析
第一种 LayoutInflater.from(context);调用方式所用源码:
@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()调用源码分析:
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);
}
查看源码可以发现:第一种调用的是第三种实例,第二种调用的是第一种实例,所以可以得到结果是:最终获取LayoutInflater都是通过第三种方式获取滴;
三、获取View : View.inflate()和LayoutInflate.inflater()
在项目中,经常需要用到将一个.xml布局文件转为View,这里有两种方式:
1、使用View的静态方法inflate,不用获取LayoutInflater,其实是源码内部同样获取了LayoutInflater:
View中的源码:
/**
* 从xml资源中填充视图,这个方法封装了LayoutInflater
* @param context 您的Activity或Application上下文对象
* @param resouce 需要填充的资源ID
* @param root 一个视图组,他将是父视图组,用于正确填充layout的参数
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
2、使用LayoutInflater.inflater():
有四种调用方式:
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) ;
查看上面四种方法调用的源码后,发现:最终调用的都是第四种方法;
在实际项目开放中,经常使用的是一下两种方法:
mLayoutInflater.inflate(@LayoutRes int resource,@Nullable ViewGroup root);
mLayoutInflater.inflate(@Layoutres int resourse,@Nullable ViewGroup root ,boolean attachToRoot);
可以看见,一个是两个参数,一个是三个参数,而两个参数的inflate,其实也是调用的三个参数的inflate,现在来看看三个参数代表的意义和使用需要注意的事项:
@LayoutRes int resource : 这个很明显,是需要传入的布局的资源ID;
@Nullable ViewGroup root : 需要附加到resource资源文件的父控件,即调用inflate方法会得到一个View对象,root参数就是接受该对象的容器;
boolean attachToRoot : 是否把inflate得到的View对象添加到root中,该参数为false时,表示不直接添加到root,true时,表示直接添加到root中;
三个参数都明白意思了,那现在再把使用的几种参数情况理解一下:
既然两个参数最终都是调用的三个参数方法,那先分析三个参数的inflate方法:
现在定义出来需要用到的布局文件:
Activity布局文件,Activity的xml布局文件activity_main:里面只有一个空了Linearlayout
再定义一个用来填充的布局文件inflatelayout.xml:
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); //进行布局填充
}
查看项目运行结果:
可以看见,已经将填充的布局添加到activty中了,这是因为第三个参数为true,表示将第一个参数所指的的布局添加到第二个参数的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);
}
上面代码实现了与attachToRoot为true一样的运行效果,现在分析一下,为false的情况:
attachToRoot为false,表示不将第一个参数所指定的布局文件添加到第二个参数的View中;在这里会有一个疑问:既然不将布局文件添加到指定的root中,那为什么不直接将第二个参数给null?这里就涉及另外一个问题:我们给控件所指定的layout_weight和layout_height到底是什么意思?该属性表示一个控件在容器中大小,就是说这个控件必须在容器中,这个属性才有意义,否则无意义!那由此我们可以等到这个结论:如果我直接将inflatelayout.xml填充而不给他指定一个父布局,则inflatelayout.xml的根节点的高宽会失效,如果想让其有效,就必须为其指定一个父容器。现在我们能明白root和attachToRoot在这里器到的作用:设置root不为空,是为了让inflatelayout.xml的根布局宽高有效,attachToRoot就是不自动将inflateLayout.xml填充到父容器中。
3、root为空:
其实滴二种情况也将root为空的给解释一下。这里在重复一下:root不为空,则填充的xml文件的根布局的宽高设置有效,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,运行结果都为下图:
因为inflate时没有指定容器,所以他个宽高失效了,但如果我设置button的宽高,则会有效,因为他是处于一个容器中的;
现在分析两个参数的情况
我们先看两个参数的调用源码:
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);
}
可以很明显看出,两个参数的inflate实际上调用的也是三个参数的inflate方法,attchToRoot根据root来判断值,所以最终的结果也就分为以下两种情况:
- root为null,等同于三个参数的:root为空,attachToRoot为false;
- root不为null,等同于三个参数的:root不为空,attachToRoot为true;
上面就是LayoutInflater的简单理解和inflate方法的使用介绍;
这里再记录分析一个问题:为什么Activity布局的根节点的宽高属性会生效?
大部分Activity页面都由两部分组成(Android版本号和应用主题会影响到Activity页面的组成,这里以常见页面为例),我们的Activity页面中有一个顶级View叫DecorView,DecorView中包含一个Vertical的LinearLayout,Linearlayout由两部分组成,第一部分为标题栏,第二部分为内容栏,内容栏是一个FrameLayout,我们在Activity中调用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();
}
可以看见再设置的时候,获取了android.R.id.content的View,并将其做为了父控件后使用的 LayoutInflater.from(mContext).inflate(resId, contentParent);所以我们设置的布局的宽高会生效;
本文参考链接:三个案例带你看懂LayoutInflater中inflate方法两个参数和三个参数的区别