前言
在自定义组合控件的时候,仅仅是通过下面的一句代码就能把xml布局显示到自定义的ViewGroup中,很好奇其内部的实现原理,便有了这篇文章。
View view = LayoutInflater.from(context).inflate(R.layout.view_layout, this, true);
主要实现原理
先看看View.inflate()的源码
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
由上面源码可见,其实View.inflate()方法,是直接调用LayoutInflater.inflate()方法,差别只是其最后的参数是通过第二个参数root来判断。我们去看看LayoutInflater.inflate()的源码。
LayoutInflater.inflate()
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
跟进去看,这里主要把resource转为XmlResourceParser,然后再调用inflate(parser, root, attachToRoot)方法。
LayoutInflater.inflate(parser, root, attachToRoot)
/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
*/
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 {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//如果是 merge 标签的布局文件,不绑定到root的话,则会抛出异常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 把xml文件中的view添加到root中
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建xml文件中的最外层View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
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);
}
}
// 解析和添加xml中的子view
rInflateChildren(parser, temp, attrs, true);
// 添加到root
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 如果root为空或者不绑定,则把temp返回
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
}
return result;
}
}
上面主要做了下面几个事情:
1、首先判断xml布局最外层是否为merge标签,如果是,则调用LayoutInflater.rInflate()方法把xml里面的view添加到root中;否则执行2;
2、创建xml文件中的最外层view;
3、解析和添加xml中的子view;
4、通过判断是否绑定root,是则返回root,否则返回2中创建的view;
接着看LayoutInflater.rInflateChildren(parser, temp, attrs, true)方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
这个方法里面,也是直接调用LayoutInflater.rInflate()方法。
现在来看主要的方法-LayoutInflater.rInflate()
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*/
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
// 不断的循环遍历xml中同层级的布局
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)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
// 实例化当前视图
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归,把当前视图当做parent
rInflateChildren(parser, view, attrs, true);
// 把当前view添加到parent中
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
通过方法上面的注释就能大概的知道这个方法的作用,大概的意思是:通过递归的方式,不断的降低xml的层级和实例化视图以及其子view,然后调用onFinishInflate()方法;
总结
通过简单的源码分析,总的来说,LayoutInflater.from(context).inflate(R.layout.view_layout, this, true); 这句代码的作用就是把xml布局添加到自定义的ViewGroup中,然后返回一个最终的View。