拿这个布局为例,让我们跟随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 * [email protected] 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)) { // 处理<merge />标签 if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } //将<merge />标签的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)) { //处理<requestFocus />标签 parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { //处理<include />标签 if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs);//解析<include />节点 } else if (TAG_MERGE.equals(name)) { //处理<merge />标签 throw new InflateException("<merge /> 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树解析完毕!