LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类。
一般来说,我们通常是动态加载view的时候会用到这个LayoutInflater.inflater();
你是否曾经因为动态加载出现了一些问题?
比如写出以下代码:
linearLayout=(LinearLayout)findViewById(R.id.linear);
button=(Button)getLayoutInflater().inflate(R.layout.button_main,null);
linearLayout.addView(button);
明明设置了如下XML布局文件:
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="50dp"
android:layout_height="50dp"
android:text="button"
/>
而layout_width和layout_height失效了呢?
先让我们走进inflater源码去分析:
我们可以看到inflater有四个重载方法:
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);
}
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 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) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
步骤1:获得resource的解析器
final XmlResourceParser parser = res.getLayout(resource);
步骤2:获得resource的属性集,并设置View result = root
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
步骤3:判断XML标签类型
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!");
}
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!");
}
步骤4: TAG_MERGE.equals(name)成立,即该根标签为<merge>,必须将其添加到父布局当中,否则报错。
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);
}
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);
}
步骤5:TAG_MERGE.equals(name)不成立,走向如下源码:
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 (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
这里就是关键:如果root!=null,则根据resouce的属性集去构造Layoutparams对象,否则不构造。且attachToRoot=false,为view色设置LayoutParmas对象,否则不设置。
步骤6:temp作递归添加子view。判断root==null以及attachToRoot==true
如果root!=null且attachToRoot=true:将view添加至父布局root当中,返回result (在步骤1已经设置result=root),所以返回值为root。
如果root=null或者attachToRoot=false:将result=temp,返回result。
所以这里分两种情况,即:
(1)root=null,返回未设置layoutParams的view
(2)root!=null,attachToRoot=false,返回设置了layoutParams的view。(在步骤5中:temp.setLayoutParams(params); )这里的view都未添加到父布局,需要我们手动去将布局添加到ViewGroup当中。
正如我开始提到的那个代码:
linearLayout=(LinearLayout)findViewById(R.id.linear);
button=(Button)getLayoutInflater().inflate(R.layout.button_main,null);
linearLayout.addView(button);
既然没有设置layoutParams,为何button还是有固定的大小?当然是在ViewGroup.addView(button)方法当中。
让我们走向源码:
public void addView(View child) {
addView(child, -1);
}
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
经过debug,发现params的确为空,说明createViewFromTag()生成的view的确没有layoutparams。
注明:由于createViewFromTag()内部最后发现是native层,就没有深入挖掘。
可以看到:child存在layoutParams,则使用child的layoutParams。
child不存在layoutParams,则执行:
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
不存在,默认layout_width=wrap_content layout_height=wrap_content;
至于这行代码,你一定很好奇对吗?
createViewFromTag(root, name, inflaterContext, attrs);
嘿嘿,当然是聪明的你去挖掘更多的东西啦!