LayoutInflater源碼解析,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可以使用自訂的Factory 方法來建立view。