LayoutInflater
在看inflate()方法时,我们随便看下如何获得 LayoutInflater ,获得LayoutInflater 实例有三种方式
LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()
LayoutInflater inflater = LayoutInflater.from(context);
LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
其实,这三种方式本质是相同的,从源码中可以看出:
- getLayoutInflater()
Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码:
1 | public PhoneWindow(Context context) |
可以看出它其实是调用 LayoutInflater.from(Context context)
方法。
- LayoutInflater.from(Context context)
1 | public static LayoutInflater from(Context context) |
可以看出它其实调用 context.getSystemService()。
所以这三种方式最终本质是都是调用的Context.getSystemService()。
接下来我们来看下inflate()方法,
inflate()
LayoutInflater的inflate方法一共有四种
public View inflate(int, ViewGroup)
public View inflate(XmlPullParser, ViewGroup)
public View inflate(int, ViewGroup, boolean)
public View inflate(XmlPullParser, ViewGroup, boolean)
查看源码我们会发现inflate(int, ViewGroup)
调用的是inflate(int, ViewGroup, boolean)
方法
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { |
而inflate(int, ViewGroup, boolean)
调用的是inflate(XmlPullParser, ViewGroup, boolean)
方法
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
再看View inflate(XmlPullParser, ViewGroup)
我们会发现它调用的也是inflate(XmlPullParser, ViewGroup, boolean)
方法
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
所以呢?这四个方法都是public View inflate(XmlPullParser, ViewGroup, boolean)
方法,那其他三个我们就不看了,我们来分析这个方法。
看下别人对这个方法的参数是怎样描述的:
- 返回值View:
返回的值是View指向的根节点。大家可能会有个疑问,第二个参数root不就是根结点吗?那返回根结点的话,不就是我们传进去的root吗?这可不一定,大家知道返回根结点的VIEW之后,继续往下看参数的具体讲解。
- 第一个参数XmlPullParser:
也就说根据其他几个方法传进来的xml布局文件在这里会被用传进来的parser进行解析 - 第二个参数root:
表示根结点,它的作用主要取决于第三个参数
- 第三个参数attachToRoot:
表示是否将转换后的VIEW直接添加在根结点上,如果是TRUE,那么在转换出来VIEW之后,内部直接会调用root.addView()来将其添加到root结点上,然后返回根结点,当然是我们传进去的ROOT结点。如果设为FALSE,那只会将XML布局转换成对应的VIEW,不会将其添加的我们传进去的root结点上。
第三个参数可能这样说比较难理解,我们来举个例子:
1 . 创建一个activity_root.xml文件,一个垂直的线性布局id为root,只有一个TextView
1 | <?xml version="1.0" encoding="utf-8"?> |
2 . 然后再建一个布局:add_layout.xml,也是一个垂直的线性布局背景颜色是红色,里面有个TextView
1 | <?xml version="1.0" encoding="utf-8"?> |
我们先来试试TRUE这个参数
1 | public class InflateDomeActivity extends Activity{ |
效果:
那如果将TRUE换为FALSE呢?
1 | public class InflateDomeActivity extends Activity{ |
效果:
可以看到,我们的主布局没有任何变化,也就是说add_layout.xml的布局没有被添加到activity_mian.xml中;
我们开头就讲过,如果attachToRoot设为false,那转换后的布局是不会被添加到root中的,会作为结果返回。
其实attachToRoot设为TRUE的代码与下面的代码是等价的:
1 | public class InflateDomeActivity extends Activity{ |
透过源码分析LayoutInflater.inflate()的过程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法源码:
1 | public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { |
我们一点点来分析:
再源码的基础上我们保留下一些核心代码来分析下
1 | public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { |
我们来逐步分析一下:
- 第一步:一进来是初始化部分:
1 | final AttributeSet attrs = Xml.asAttributeSet(parser); |
//注意这里,在初始化时,result表示要返回的视图,默认是返回root
View result = root;
通过XML文件获取布局中各个控件的属性集:AttributeSet,注意:这里通过XML只能知道布局里每个控件的布局参数。那整个LAYOUT的布局参数呢,虽然我们在XML的根结点的布局可能写的是layout_width:fill_parent,layout_height:fill_parent。但这只是很笼统的,系统要确实计算出它的宽高数来。这个宽高数的计算就是通过root中的属性来得出来的,下面代码中会提到。
一个很重要的部分在于最后一句话!!!
[java] view plain copy
View result = root;
result表示最后返回的视图VIEW,所以这表示在默认情况下,返回root的视图!!注意这只是在默认情况下,下面会对result进行赋值的!
第二步:创建XML对应的空白视图temp
[java] view plain copy
- //第二步:创建XML对应的空白VIEW:temp
1 | if (TAG_MERGE.equals(name)) { |
在这里首先判断XML的根结点是不是merge标签,大家知道我们的merge标签的主要作用是用来将merge标签下的所有控件合并到其上层布局上,也就是说merge标签下是没有根控件的!因为merge标签下的控件都是并列关系,所以如果merge标签使用的inflate函数,那我们根本没有办法给它返回根视图,所以肯定是要抛出异常的
如果不是merge标签,就创建一个空白视图,返回给temp,这里的temp就是我们XML所对应的布局!
- 第三步:获取root的布局参数,设置到temp中
1 | //第三步:从根结点中,获取布局参数,设置到temp中 |
在这里看到,首先获取到了ROOT的布局参数赋值为params,然后当attachToRoot为FALSE时,将参数设置到temp中;
- 第四步:初始化temp中的子控件
[java] view plain copy
//第四步:初始化temp中的子控件
1 | rInflate(parser, temp, attrs, true); |
- 第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中
1 | if (root != null && attachToRoot) { |
在这里,就是当root不为空、attachToRoot为TRUE时,将构建好的temp视图添加到root中,注意这里的参数仍然从root中获取的布局参数params!!!所以,无论attachToRoot值为如何,temp的布局参数都是从ROOT里获取的!!!!
- 第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回
1 | if (root == null || !attachToRoot) { |
从这里可以看到,如果root为空,获取attachToRoot为FALSE,就会将temp做为结果返回!
到这里整个过程就分析结束了,下面我们总结一下:
- root的最基本作用,就是给我们传进去的Layout提供布局参数信息
- 如果attachToRoot为TRUE,那么会将Layout产生的布局视图添加到root中,返回root,如果attachToRoot为FALSE,那么会将Layout产生的布局视图直接返回