上一篇博客讲到setContentView
最后会调用mLayoutInflater.inflate
来创建了自定义xml中的布局视图,添加到mContentParent
中,这里我们就来学习下inflate
的具体实现以及它的基本使用方法。
首先我们需要明确的是,inflate
方法是讲xml
文件反射成一个View
,但是并不执行View的绘制。
inflate
常用的重载有两种方式:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
所以我们只需要知道第二种重载的参数意义就OK了:
resource
:需要反射的xml文件的资源idroot
:关于root我们需要注意的是,它并不代表将创建好的布局加入到root中,而是表示将root作为父容器来创建指定的View。将root作为父容器创建View,其含义是让root协助View的根节点生成布局参数,如果没有父容器的话,View的根节点的宽高属性(match_parent,wrap_parent等)将没任何的意义,即不会产生任何的效果
attachToRoot
:是否将创建好的View
添加到root
中通常我们使用inflate
有以下三种方式:
LayoutInflater.inflate
:在获取到LayoutInflater
实例后,可以通过如下代码加载布局: LayoutInflater inflater = LayoutInflater.from(this);
View view1 = inflater.inflate(R.layout.view1, null);
View view2 = inflater.inflate(R.layout.view2, null, false);
View.inflate
:只有一种形式如下:public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
其实质是通过LayoutInflater
来创建View的
Context.getSystemService
:通过Context.getSystemService
来获得LayoutInflater
,实质也是通过LayoutInflater
来创建ViewLayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view1 = inflater.inflate(R.layout.view1, null);
现在我们就来详细的分析下inflate的整个流程,源码如下:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//省略debug相关代码
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
上面代码中关键的地方在于res.getLayout(resource)
返回了一个XmlResourceParser
,XmlResourceParser
是继承了XmlPullParser
和AttributeSet
两个接口的接口,其实现类是Parser
。
(关于AttributeSet和XmlPullParser的内容详见View绘制体系(三)——AttributeSet与TypedArray详解)
而Resources
类中提供了getLayout
,getXml
,getAnim
等方法解析对应资源id的xml文件,返回对应的解析器Parser。
接下来我们来看下inflate(parser, root, 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;
// 找到第一个tag
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
// 找不到tag,抛出异常
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
// merge标签的处理
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" 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) {
//如果root不为空,获取它的布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//如果不添加到root中,则调用setLayoutParams给temp设置布局参数
//否则,使用addView(View, LayoutParmas)来设置布局参数(在下面)
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.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) {
//省略异常捕获的代码
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
上述代码,首先是attrs = Xml.asAttributeSet(parser)
,这是Xml
类提供的根据XmlPullParser
生成对应的AttributeSet
的方法。
然后就是一些xml解析的判断与循环,并且获取根节点即xml文件第一个tag位的属性:
如果为merge
标签,那么必须将该view放入一个父容器之中,即root
不为空且attachToRoot
为true。然后调用rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate)
方法,它会递归地遍历xml的层次结构并创建对应的View,创建完后根据传入的最后一个参数finishInflate
决定是否在创建完成后调用parent.onFinishInflate
方法。
创建好之后直接返回root
如果不为merge
标签,那么调用createViewFromTag
创建根节点的视图temp
,如果root不为空,且attachToRoot
为false,那么就将root的LayoutParams
传给temp
。接下来就是调用rInflateChildren
以temp为根节点递归地去创建视图。
创建好了之后,如果attachToRoot
为true,则调用addView
将创建好的temp
添加到root中,返回root,否则不添加,直接返回temp
。
在inflate
中会调用rInflate
和rInflateChildren
来递归的创建视图,我们来看下他们的源码:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//获取当前元素在View树中的深度,根节点深度为1
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
//1.如果没有遇到结束标志(/>),则继续循环下去
//2.如果遇到结束标志,但没有到文档末尾,且当前深度大于起始节点深度,则继续循环下去
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();
//根据name设置对应属性
if (TAG_REQUEST_FOCUS.equals(name)) {
//如果遇到requestFocus标签,那么使当前元素获得焦点
pendingRequestFocus = true;
//消费掉requestFocus标签下的所有子标签
//该方法内部是一个空循环
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//处理tag标签
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//处理include标签
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//如果遇见merge标签则抛出异常
throw new InflateException(" must be the root element");
} else {
//创建对应的View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//递归创建view下面的所有子view
rInflateChildren(parser, view, attrs, true);
//将创建好的View添加到parent中
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
//给包含requestFocus标签的父元素设置焦点
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
从上述源码可以看出,rInflateChildren
实质就是调用了rInflate
方法,个人认为区别可能在于处理merge
标签是传入的Context
参数,并且rInflateChildren
只用于创建parent下面的子view,不用来创建根节点view。
而rInflate
方法的主要就是一个循环,判断是否已经处理完parent view下面的所有子View(通过深度和eventType来判断的),如果没有,则根据获取的标签类型依次处理。在处理一般标签(非requestFocus,merge,include,tag)标签时,通过createViewFromTag
来创建view,并添加到parent上
以上就是LayoutInflater的inflate创建View机制的解析了,后续会继续介绍View绘制的相关知识,敬请关注!