拿这个布局为例,让我们跟随eclipse进入解析xml成view树的代码;
先上一段熟悉的代码:
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1、该方法最终也会调用到 LayoutInflater的inflate()方法中去解析。
setContentView(R.layout.main);
//2、使用常见的API方法去解析xml布局文件,
LayoutInflater layoutInflater = (LayoutInflater)getSystemService();
View root = layoutInflater.inflate(R.layout.main, null,false);
}
我们知道,在activity的onCreate里面设置布局文件的id,那么这个activity显示的就是这个布局文件。当我们设置了layout之后还是会调用LayoutInflater的inflate去解析xml文件成为view树。
先看看inflate函数:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
//获取一个XmlResourceParser来解析XML文件---布局文件。
//XmlResourceParser类以及xml是如何解析的,大家自己有兴趣找找。
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
接着跟进去
public abstract class LayoutInflater {
...
/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
*/
//我们传递过来的参数如下: root 为null , attachToRoot为false 。
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; //该mConstructorArgs属性最后会作为参数传递给View的构造函数
View result = root; //根View
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
...
final String name = parser.getName(); //节点名,即API中的控件或者自定义View完整限定名。
if (TAG_MERGE.equals(name)) { // 处理标签
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//将标签的View树添加至root中,该函数稍后讲到。
rInflate(parser, root, attrs);
} else {
// Temp is the root view that was found in the xml
//创建该xml布局文件所对应的根View。
View temp = createViewFromTag(name, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
//根据AttributeSet属性获得一个LayoutParams实例,记住调用者为root。
params = root.generateLayoutParams(attrs);
if (!attachToRoot) { //重新设置temp的LayoutParams
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// Inflate all children under temp
//添加所有其子节点,即添加所有字View
rInflate(parser, temp, attrs);
// 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;
}
}
}
...
return result;
}
}
}
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)先会判断root是否为null,如果是null,则先创建根节点,然后调用rInflate,否则直接调用rInflate。
下面我们来看一下rInflate函数
**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
*/
//递归调用每个字节点
private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)
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(" cannot be the root element");
}
parseInclude(parser, parent, attrs);//解析节点
} else if (TAG_MERGE.equals(name)) { //处理标签
throw new InflateException(" must be the root element");
} else {
//根据节点名构建一个View实例对象
final View view = createViewFromTag(name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
//调用generateLayoutParams()方法返回一个LayoutParams实例对象,
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs); //继续递归调用
viewGroup.addView(view, params); //OK,将该View以特定LayoutParams值添加至父View中
}
}
parent.onFinishInflate(); //完成了解析过程,通知....
}
这是要解析的布局
对应的布局文件为:
<1>
<2>
<3>
3>
<4>
4>
2>
<5>
<6>
6>
<7>
7>
5>
<8>
<9>
9>
<10>
10>
8>
<11>
<12>
12>
<13>
13>
11>
1>
xml文件解析成树之后的图形:
当前xml对应 rInflate函数解析的第一步就是控件2(1是根节点,在inflate里面已经生成),然后执行
//根据节点名构建一个View实例对象
final View view = createViewFromTag(name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
//调用generateLayoutParams()方法返回一个LayoutParams实例对象,
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs); //继续递归调用
viewGroup.addView(view, params); //OK,将该View以特定LayoutParams值添加至父View中
注明:接下来以上代码用“代码片段1”来表示
先是生成控件2,然后递归调用rInflate函数(把自己当成parent,和同一个parser对象传过去,这时parser已经解析过2了)
又是执行“代码片段1”,这时parent是控件2, parser.next()后得到3的attr,3也去执行“代码片段1”但是在while判断的时候由于4的深度和3是一样的(都是3),所以直接返回,于是在3执行“代码片段1”的最后一句2把3添加了进去(2是parent)并回到2。
这时parser.next()过了3了, 根节点调用的“代码片段1”会继续调用rInflate函数并把2作为parent传入,由于4和3一样,在生成自己的view之后调用Inflate执行while的时候发现5的深度(值为2)不比自己大,被2添加到试图之后而结束递归。
结束之后2被添加到了parent(就是根节点1),这时根节点调用的 “代码片段1”中只执行了一次while.
然后再执行一次while到5,5添加了6、7,后再被1添加,这时根节点调用的 “代码片段1”中执行了两次while.
然后再执行一次while到8,8添加了9、10之后被1添加,这时根节点调用的 “代码片段1”中执行了三次while.
然后再执行一次while到11,11添加了12、13之后被1添加。这时根节点调用的 “代码片段1”中执行了四次while.
最后在执行第五次while判断的时候由于
type != XmlPullParser.END_DOCUMENT
而结束。
至此:整颗view树解析完毕!