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()方法,看一下该源代码:
public PhoneWindow(Context context)
{
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
可以看出它其实是调用 LayoutInflater.from(Context context)
方法。
- LayoutInflater.from(Context 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;
}
可以看出它其实调用 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)
方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
而inflate(int, ViewGroup, boolean)
调用的是inflate(XmlPullParser, ViewGroup, boolean)
方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
再看View inflate(XmlPullParser, ViewGroup)
我们会发现它调用的也是inflate(XmlPullParser, ViewGroup, boolean)
方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
所以呢?这四个方法都是
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
android:id="@+id/root">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
2 . 然后再建一个布局:add_layout.xml,也是一个垂直的线性布局背景颜色是红色,里面有个TextView
<?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:background="#f00">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="30dp"
android:gravity="center"
android:text="我是附加的" />
</LinearLayout>
我们先来试试TRUE这个参数
public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
//显示activity_root布局
setContentView(R.layout.activity_root);
//通过LayoutInflater.from(Context context);来获取LayoutInflater的实例
LayoutInflater layoutInflater = LayoutInflater.from(this);
//获取根结点的控件实例
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
//将activity_add加载到activity_root布局中
layoutInflater.inflate(R.layout.activity_add,linearLayout,true);
}
}
效果:
那如果将TRUE换为FALSE呢?
public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_root);
LayoutInflater layoutInflater = LayoutInflater.from(this);
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
layoutInflater.inflate(R.layout.activity_add,linearLayout,false);
}
}
效果:
可以看到,我们的主布局没有任何变化,也就是说add_layout.xml的布局没有被添加到activity_mian.xml中;
我们开头就讲过,如果attachToRoot设为false,那转换后的布局是不会被添加到root中的,会作为结果返回。
其实attachToRoot设为TRUE的代码与下面的代码是等价的:
public class InflateDomeActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_root);
LayoutInflater layoutInflater = LayoutInflater.from(this);
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.root);
View view = layoutInflater.inflate(R.layout.activity_add, linearLayout, false);
linearLayout.addView(view);
}
}
透过源码分析LayoutInflater.inflate()的过程
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)方法源码:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
我们一点点来分析:
再源码的基础上我们保留下一些核心代码来分析下
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//第一步:初始化
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//注意这里,在初始化时,result表示要返回的视图,默认是返回root
View result = root;
…………
final String name = parser.getName();
//第二步:创建XML对应的空白VIEW:temp
if (TAG_MERGE.equals(name)) {
//如果是merge标签:抛出异常 (因为merge标签能够将该标签中的所有控件直接连在上一级布局上面,从而减少布局层级,假如一个线性布局替换为merge标签,那么原线性布局下的多个控件将直接连在上一层结构上,也就是如果加载进来的root根节点是root的话,那么将来无法知道布局的根节点是什么)
…………
} else {
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
//第三步:从根结点中,获取布局参数,设置到temp中
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
//第四步:初始化temp中的子控件
rInflate(parser, temp, attrs, true);
//第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
我们来逐步分析一下:
- 第一步:一进来是初始化部分:
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//注意这里,在初始化时,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
if (TAG_MERGE.equals(name)) {
//如果是merge标签,抛出异常
…………
} else {
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
在这里首先判断XML的根结点是不是merge标签,大家知道我们的merge标签的主要作用是用来将merge标签下的所有控件合并到其上层布局上,也就是说merge标签下是没有根控件的!因为merge标签下的控件都是并列关系,所以如果merge标签使用的inflate函数,那我们根本没有办法给它返回根视图,所以肯定是要抛出异常的
如果不是merge标签,就创建一个空白视图,返回给temp,这里的temp就是我们XML所对应的布局!
- 第三步:获取root的布局参数,设置到temp中
//第三步:从根结点中,获取布局参数,设置到temp中
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
在这里看到,首先获取到了ROOT的布局参数赋值为params,然后当attachToRoot为FALSE时,将参数设置到temp中;
- 第四步:初始化temp中的子控件
[java] view plain copy
//第四步:初始化temp中的子控件
rInflate(parser, temp, attrs, true);
//rInflate()其实是一个递归函数,用来递归建立temp下的所有控件的视图
- 第五步:如果root不为空,而且attachToRoot设为TRUE,则将其视图通过addView添加到root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
在这里,就是当root不为空、attachToRoot为TRUE时,将构建好的temp视图添加到root中,注意这里的参数仍然从root中获取的布局参数params!!!所以,无论attachToRoot值为如何,temp的布局参数都是从ROOT里获取的!!!!
- 第六步:如果root为空,或者attachToRoot设为FALSE,那么就将TEMP视图做为result返回
if (root == null || !attachToRoot) {
result = temp;
}
从这里可以看到,如果root为空,获取attachToRoot为FALSE,就会将temp做为结果返回!
到这里整个过程就分析结束了,下面我们总结一下:
- root的最基本作用,就是给我们传进去的Layout提供布局参数信息
如果attachToRoot为TRUE,那么会将Layout产生的布局视图添加到root中,返回root,如果attachToRoot为FALSE,那么会将Layout产生的布局视图直接返回
我的博客网站:http://huyuxin.top/欢迎大家访问!评论!