转载请说明来自:http://blog.csdn.net/super_kingking/article/details/51983011
我们在开发过程中都会用到加载xml布局文件,主要常用的俩中情况是 :1.在activity的setContentView(),2.用LayoutInflater解析xml文件,主要使用是在listView,scrollerView,动态添加布局文件等等使用。
今天主要来看一下LayoutInflater的工作原理,并从源码角度解读LayoutInflater的工作机制。
1.获取LayoutInflater的方法。
LayoutInflater的获取有方式有俩种。
1、LayoutInflater inflater = LayoutInflater.from(this);
2、LayoutInflater inflater2 = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
其实这俩种写法是一样的,我们来查看一下LayoutInflater.from(this)源码:
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
.............................................
return LayoutInflater;
}
通过源码我们可以看出,第一种方法是对第二种方法的封装,使用更多方便。
2.LayoutInflater的基本用法
解析xml布局常用的三种方式:
1、inflate(layoutId, null )、
2、inflate(layoutId, root, false)
3、inflate(layoutId, root, true )
下面通过demo分析三者之间的区别,在MainActivity的布局文件activity_main.xml是:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linear_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
分别设置俩个子xml布局文.1、button_no_layout.xml:
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/btn_cell"
android:layout_width="120dp"
android:layout_height="120dp" />
2、button_with_layout.layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_cell"
android:layout_width="120dp"
android:layout_height="120dp" />
</LinearLayout>
将button_no_layout.xml和button_with_layout.xml依次添加到activity_main.xml的linearlayout中。下面编辑MainActivity.java代码。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_with_layout, null);
linear_content.addView(view);
}
运行显示效果:
现在将button_with_layout.xml换掉,更换button_no_layout.xml,运行查看结果:
结论:button外面没有包裹layout的xml文件,并没有正常显示
现在调用第二个方法inflate(layoutId, root, false)方法,代码如下:
1、inflater.inflate(R.layout.button_with_layout, linear_content, false);
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_with_layout, linear_content, false);
linear_content.addView(view);
2、inflater.inflate(R.layout.button_no_layout, linear_content, false)
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_no_layout, linear_content, false);
linear_content.addView(view);
俩者显示结果是一样的:
结论:均能正常显示button的大小
现在调用第三个方法inflate(layoutId, root, true),代码如下:
1、inflater.inflate(R.layout.button_with_layout, linear_content, true);
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_with_layout, linear_content, true);
linear_content.addView(view);
2、inflater.inflate(R.layout.button_no_layout, linear_content, true)
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_no_layout, linear_content, true);
linear_content.addView(view);
运行崩溃:java.lang.IllegalStateException: The specified child already has a parent.
You must call removeView() on the child's parent first
运行结果总结:
1.调用第一种方法:inflate(layoutId, null )时,解析并添加button_no_layout.xml布局时,button并没有按照设置的宽高120dp显示。而button_with_layout.xml正常显示了。
2.调用第二种方法时,二者均正常显示。
3、调用第三种方法时,直接无法运行崩溃掉了。
下面让我们从源码的角度解释:
首先进入inflate(layoutId, null )源码,
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
此时,原来该方法内部的本质也是第二,第三种方法,都是调用了,
inflate(int resource, ViewGroup root, boolean attachToRoot)
只是此时的root==null,attachToRoot等于false.
再往下看:
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;
**//xml解析**
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;
}
}
代码比较长看重点:
View result = root;
.......................
// Temp is the root view that was found in the xml
//解析xml获取的根(root)View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//如果root不为空,并且attachToRoot为false.对应的是.inflate(layoutId, root, false )
if (root != null) {
........................
//获取root的params参数
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
temp.setLayoutParams(params);
}
}
.............................
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
.......................
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//如果root不为空,attachToRoot为true.对应的是inflate(layoutId, root, true )
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) {
//如果root等于null,护着attachToRoot为false,对应的是inflate(layoutId, null)
//直接返回的是temp
result = temp;
}
}
}
1、如果root为null,直接返回解析xml的根目录temp.
layoutId布局外面存在包裹的容器layout,则layoutId布局的宽高正常显示,如果不存在layout,则layoutId布局不能正常显示
2、如果root不空,attachToRoot为false,temp设置了root的params属性值。 所以layoutId布局外面无论有没有layout包围,均能够正常显示。
3、如果root不为空,并且attachToRoot为true,root.addview()添加解析的布局文件
我们把linear_content作为root值传递进来,在inflater的时候已经linear_content(root).addView()添加过一次,
在代码中:
LinearLayout linear_content = (LinearLayout) findViewById(R.id.linear_content);
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.button_no_layout,null);
linear_content.addView(view);重复添加,异常直接崩溃。
综上所述可以看出,我们在实际开发过程中:
1、如果xml中包含了Layout容器,则可以添加使用inflate(layoutId, null )、 inflate(layoutId, root, false),
2、如果xml布局文件没有layout容器,布局文件要想正常显示则必须给layoutId布局添加Root,设置root的params…使用inflate(layoutId, root, false) ,inflate(layoutId, root, true ),
3、需要注意的是在使用inflate(layoutId, root, true )时layoutId布局文件会被添加到root当中,所以inflate(xxx)获得的View不可重复添加到root
在activity中setContentView(layoutId),layoutId布局其实调用的也是inflater.inflate(layoutId,root),我看一下源码解释。
在activity代码中:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow()获取window,window是个抽象类“public abstract class Window ”,phoneWindow是window的实现类,
查看代码分析:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
可以看出setContentView()内部实现也是通过LayoutInflater,调用inflater.inflate(layoutId,root),root不为空,所以即使 activity_main.xml没有Layout容器,该布局的控件也能正常显示。
总结使用listView的时重写adapter的getView()方法时LayoutInflater的用法
1.
convertView = inflater.inflate(R.layout.button_with_layout, parent, false);
convertView = inflater.inflate(R.layout.button_with_layout,null);
能够正常显示,每个button外面都包裹一个LinearLayout。listView的item是LinearLayout。
item的层级结构
2.
convertView = inflater.inflate(R.layout.button_no_layout,null);
button不能正常显示.因为没有Layout容器包裹。无法测量button的大小。
3.convertView = inflater.inflate(R.layout.button_no_layout,parent,false);
button正常显示,item是button,层级少。
4.convertView = inflater.inflate(R.layout.button_no_layout,parent,true);
convertView = inflater.inflate(R.layout.button_with_layout,parent,true);
崩溃:
addView(View, LayoutParams) is not supported in AdapterView
listView是adapterView的子类。listView不支持addView()就是这个原因。