LayoutInflater源码解析

Android使用LayoutInflater来进行布局加载,通常获取方式有两种:

第一种:

LayoutInflater layoutInflater = LayoutInflater.from(context); 

第二种:

LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 

从源码中可以看出第一种是第二种的封装简化,便于使用:

1 public static LayoutInflater from(Context context) {
2         LayoutInflater LayoutInflater =
3                 (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
4         if (LayoutInflater == null) {
5             throw new AssertionError("LayoutInflater not found.");
6         }
7         return LayoutInflater;
8     }

我们通过调用inflate方法便可以完成对布局的加载:

layoutInflater.inflate(resource, root, true);  

LayoutInflater中的inflate方法有若干种重载方式,最终都调用了如下代码:

  1 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  2         synchronized (mConstructorArgs) {
  3             //获取xml中属性信息
  4             final AttributeSet attrs = Xml.asAttributeSet(parser);
  5             Context lastContext = (Context)mConstructorArgs[0];
  6             mConstructorArgs[0] = mContext;
  7             View result = root;
  8 
  9             try {
 10                 // 查找根节点.
 11                 int type;
 12                 while ((type = parser.next()) != XmlPullParser.START_TAG &&
 13                         type != XmlPullParser.END_DOCUMENT) {
 14                     // Empty
 15                 }
 16                 
 17                 if (type != XmlPullParser.START_TAG) {
 18                     throw new InflateException(parser.getPositionDescription()
 19                             + ": No start tag found!");
 20                 }
 21                 //获取根节点名称
 22                 final String name = parser.getName();
 23                 
 24                 if (DEBUG) {
 25                     System.out.println("**************************");
 26                     System.out.println("Creating root view: "
 27                             + name);
 28                     System.out.println("**************************");
 29                 }
 30                 //如果是merge标签,必须保证父节点不为null且attachToRoot为true
 31                 if (TAG_MERGE.equals(name)) {
 32                     if (root == null || !attachToRoot) {
 33                         throw new InflateException("<merge /> can be used only with a valid "
 34                                 + "ViewGroup root and attachToRoot=true");
 35                     }
 36 
 37                     rInflate(parser, root, attrs, false);
 38                 } else {
 39                     //代表布局文件中根节点的view
 40                     View temp;
 41                     if (TAG_1995.equals(name)) {
 42                         temp = new BlinkLayout(mContext, attrs);
 43                     } else {
 44                         //利用反射,通过root名称创建view
 45                         temp = createViewFromTag(root, name, attrs);
 46                     }
 47 
 48                     ViewGroup.LayoutParams params = null;
 49 
 50                     if (root != null) {
 51                         if (DEBUG) {
 52                             System.out.println("Creating params from root: " +
 53                                     root);
 54                         }
 55                         // Create layout params that match root, if supplied
 56                         //当提供了父容器时,由父容器根据属性值创建布局参数
 57                         params = root.generateLayoutParams(attrs);
 58                         if (!attachToRoot) {
 59                             // Set the layout params for temp if we are not
 60                             // attaching. (If we are, we use addView, below)
 61                             //当不把当前view附加到父容器中,则设置获取到的布局参数
 62                            //否则使用下面的addView方法设置
 63                             temp.setLayoutParams(params);
 64                         }
 65                     }
 66 
 67                     if (DEBUG) {
 68                         System.out.println("-----> start inflating children");
 69                     }
 70                     // Inflate all children under temp
 71                    //递归调用此方法加载子布局
 72                     rInflate(parser, temp, attrs, true);
 73                     if (DEBUG) {
 74                         System.out.println("-----> done inflating children");
 75                     }
 76 
 77                     // We are supposed to attach all the views we found (int temp)
 78                     // to root. Do that now.
 79                     if (root != null && attachToRoot) {
 80                         root.addView(temp, params);
 81                     }
 82 
 83                     // Decide whether to return the root that was passed in or the
 84                     // top view found in xml.
 85                     if (root == null || !attachToRoot) {
 86                         result = temp;
 87                     }
 88                 }
 89 
 90             } catch (XmlPullParserException e) {
 91                 InflateException ex = new InflateException(e.getMessage());
 92                 ex.initCause(e);
 93                 throw ex;
 94             } catch (IOException e) {
 95                 InflateException ex = new InflateException(
 96                         parser.getPositionDescription()
 97                         + ": " + e.getMessage());
 98                 ex.initCause(e);
 99                 throw ex;
100             } finally {
101                 // Don't retain static reference on context.
102                 mConstructorArgs[0] = lastContext;
103                 mConstructorArgs[1] = null;
104             }
105 
106             return result;
107         }
108     }

这里,Android使用了PULL来解析xml布局文件,并通过反射来创建出当前view:

temp = createViewFromTag(root, name, attrs);

 

我们查看一下源码:

 1 View createViewFromTag(View parent, String name, AttributeSet attrs) {
 2         if (name.equals("view")) {
 3             name = attrs.getAttributeValue(null, "class");
 4         }
 5 
 6         if (DEBUG) System.out.println("******** Creating view: " + name);
 7 
 8         try {
 9             View view;
10             if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
11             else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
12             else view = null;
13 
14             if (view == null && mPrivateFactory != null) {
15                 view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
16             }
17             
18             if (view == null) {
19                 if (-1 == name.indexOf('.')) {
20                     view = onCreateView(parent, name, attrs);
21                 } else {
22                     view = createView(name, null, attrs);
23                 }
24             }
25 
26             if (DEBUG) System.out.println("Created view is: " + view);
27             return view;
28 
29         } catch (InflateException e) {
30             throw e;
31 
32         } catch (ClassNotFoundException e) {
33             InflateException ie = new InflateException(attrs.getPositionDescription()
34                     + ": Error inflating class " + name);
35             ie.initCause(e);
36             throw ie;
37 
38         } catch (Exception e) {
39             InflateException ie = new InflateException(attrs.getPositionDescription()
40                     + ": Error inflating class " + name);
41             ie.initCause(e);
42             throw ie;
43         }
44     }

里面根据不同情况,调用了onCreateView方法,利用反射来创建view。其中可以使用指定的factory来创建view,这样的钩子设计使得inflate方法变得十分灵活。

然后调用rInflate(parser, temp, attrs, true)方法来递归查找temp中的子view,并添加到上层view中:

 1 void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,
 2             boolean finishInflate) throws XmlPullParserException, IOException {
 3 
 4         final int depth = parser.getDepth();
 5         int type;
 6 
 7         while (((type = parser.next()) != XmlPullParser.END_TAG ||
 8                 parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
 9 
10             if (type != XmlPullParser.START_TAG) {
11                 continue;
12             }
13 
14             final String name = parser.getName();
15             
16             if (TAG_REQUEST_FOCUS.equals(name)) {
17                 parseRequestFocus(parser, parent);
18             } else if (TAG_INCLUDE.equals(name)) {
19                 if (parser.getDepth() == 0) {
20                     throw new InflateException("<include /> cannot be the root element");
21                 }
22                 parseInclude(parser, parent, attrs);
23             } else if (TAG_MERGE.equals(name)) {
24                 throw new InflateException("<merge /> must be the root element");
25             } else if (TAG_1995.equals(name)) {
26                 final View view = new BlinkLayout(mContext, attrs);
27                 final ViewGroup viewGroup = (ViewGroup) parent;
28                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
29                 rInflate(parser, view, attrs, true);
30                 viewGroup.addView(view, params);                
31             } else {
32                 final View view = createViewFromTag(parent, name, attrs);
33                 final ViewGroup viewGroup = (ViewGroup) parent;
34                 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
35                 rInflate(parser, view, attrs, true);
36                 viewGroup.addView(view, params);
37             }
38         }
39 
40         if (finishInflate) parent.onFinishInflate();
41     }

里面也用到onCreateView方法创建子view,然后将其加入到父view中返回。

通过查看上面的源码,我们可以发现inflate方法中的三个参数int resource, ViewGroup root, boolean attachToRoot的作用如下:

resource指定了要加载的view,root作为view外面一层的父容器,attachToRoot表示是否将view加入到父容器。

当指定了父容器,并且attachToRoot为true,则将view加入到父容器中。

如果指定了父容器,却将attachToRoot设置为false,那么只是从父容器中生成了view布局的参数并设置给view

当未指定父容器时,直接返回view本身。

 

总结

通过研究LayoutInflater源码的设计,我们了解到代码的执行细节的同时,也可以发现:

LayoutInflater创建view对象时候使用了简单工厂模式,并通过加入钩子方法,利用抽象工厂模式让coder可以使用自定义的工厂方法来创建view

 

posted @ 2015-08-05 23:06  CQUMonk  阅读(486)  评论(1编辑  收藏  举报