1. 介绍
这是Android文档对于LayoutInflater这个类的说明,这个类主要功能是把一个xml文件转换为有一定层次关系的View树。 LayoutInflater类的作用十分大,整体上说是由它解析xml并构建了整个DecorView的View层次结构,同时由于这个类对于Factory的设计,促成了我们比较常用的Fragment。本篇主要是对LayoutInflater类加载View的过程进行分析。
2. 源码分析
LayoutInflater类主要的使用场景是Activity内部setContentView函数和我们自己加载一个View的时候。
Activity使用场景
首先先看下Activity内部的使用流程,
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
Activity的setContentView方法内部会调用Window的setContentView,手机上默认是PhoneWindow,再看下PhoneWindow的setContentView内部的逻辑
@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();
}
}
其中第3-7行:主要是创建一个DecorView,然后根据主题的配置加载不同的xml并添加到这个DecorView上面,这些xml有一个共同的特征是内部都包含一个id=content的FrameLayout,我们在IDE编辑的xml布局其实就是添加到这个FrameLayout上面,本篇主要是介绍LayoutInflater类的功能,此处的具体细节不作过多描述!
第8行:调用LayoutInflater类的inflate方法将我们在IDE中编辑的xml布局添加到上面介绍的ID=content的这个FrameLayout上面
- 自主加载View
加载布局的时候通常会调用两个方法,一个是直接使用LayoutInflater类的
inflate(int resource, ViewGroup root, boolean attachToRoot)方法
另外一个会使用View类的inflate方法
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
- LayoutInflater类inflate方法分析
上面的Activity类的setContentView调用还是自己去加载View,最后都是调用到了LayoutInflater类的inflate方法,LayoutInflater内部重载了好几种参数类型的inflate方法,最后都是调用到这个函数
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;
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, attrs, false);
} else {
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, 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
rInflate(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 (IOException 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;
}
return result;
}
}
其中30-36行:处理Merge节点逻辑,由于我们在IDE编辑的xml布局最终会添加到id=Content的FrameLayout上面,所以当编辑的xml根布局为FrameLayout的时候就可以使用merge进行布局优化,在这里36行直接以root为参数调用rInflate(parser, root, attrs, false)方法进行递归添加子View,这个方法在后面会进行详细分析。
其中39-44行:其中40行判断当前是否是blink节点,这个节点和其对应的BlinkLayout在Sdk中并没有开放,真心是不知道干嘛的,此处略过。其中43行会解析我们在IDE编辑的xml根节点并通过createViewFromTag方法通过反射生成一个View类的temp实例。
其中48-60行:假如参数attachToRoot为false,会把root生成的LayoutParam设置到temp实例上。
其中66行:还是以temp为参数调用rInflate(parser, temp, attrs, true)方法递归生成View
其中73-75行:根据root是否为null和attachToRoot来判断是否将加载的View树添加root上面
这样View树加载流程大体上就介绍完了,再看下其他几个在加载过程中使用的方法。
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
if (DEBUG) System.out.println("******** Creating view: " + name);
try {
View view;
if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
else view = null;
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
}
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
}
if (DEBUG) System.out.println("Created view is: " + view);
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
该函数的具体功能是通过反射指定节点生成一个View实例
第2-4行:处理的是普通View节点
第9-16行:通过Factory的相关接口大大拓展了布局xml节点的范围,不仅可以加载View还可以加载Fragment等节点。
第18-24行:通过判断节点中 “.”分割符位置来判断是一个系统控件还是一个自定义控件,系统控件我们只会编写名称,系统会给我们自动添加”android.view.”前缀,具体细节在onCreateView方法内定义
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
final View view = new BlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) parent.onFinishInflate();
}
该方法的主要作用是递归遍历xml全部节点已经View 的过程;
其中17行:处理request-focus节点
其中19-22行:处理include节点的过程,这个节点平时开发用的比较多
其中24行:处理merge节点。这里抛出了一个异常,可以看出只有根节点才可以使用merge
其中26-30行:又是关于对BlinkLayout的处理
其中32-36行:这个是普通情况了,先生成该节点的实例,然后将该实例作为parent参与递归调用!
3. 总结
通过上面的分析了LayoutInflater解析xml节点生成View树的过程,主要还是attachToRoot变量的使用
为true是加载的View树会被添加到root上面,为false的时候用来为xml根节点对应的View生成对应的LayoutParams!