一、獲取LayoutInflater的三種方法
1、
LayoutInflater layoutInflater = (LayoutInflater) MainActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2、
LayoutInflater layoutInflater = LayoutInflater.from(MainActivity.this);
3、
LayoutInflater layoutInflater = MainActivity.this.getLayoutInflater();
其實查看它們的源碼就會發現,后兩種方法最終也還是調用第一種方法的context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)。不過查看第三種方法的源碼要稍微繞一下,因為activity.getLayoutInflater(),其實是調用Window類的getLayoutInflater(),而這個是抽象類。根據這個類的注釋,可以查看它的子類PhoneWindow,而這個是內部類,用開發工具是找不到這個類的,要手動去找。可以從sdk的文件目錄下找,例如,我的是“D:\Android\Sdk\sources\android-23\com\android\internal\policy”。也可以直接在sdk目錄下,搜索該類就行了。
其實View有一個靜態的inflate方法,連LayoutInflater對象都幫你內部創建了。其方法源碼如下:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root); }
二、inflate方法的一些參數問題
以前是困惑於inflate方法的作用。現在是困惑於什么時候才傳入ViewGroup對象和attachToRoot的取值。所以分析了一下源碼。
當寫ListView的Adapter時,總要在getView方法里調用inflate方法,例如:
convertView = LayoutInflater.from(mContext).inflate(R.layout.push_to_refresh_header, parent, false);
這個inflate方法的源碼是:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
這個時候傳入的ViewGroup對象不為nullt,同時設置boolean attachToRoot為false。
而在動態添加布局的時候,我學到的是下面這種寫法:
View refreshView = layoutInflater.inflate(R.layout.push_to_refresh_header, null); linearLayout.addView(refreshView);
這個inflate方法的源碼是:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
這個時候傳入的ViewGroup對象為null。
可以看到,當調用兩個參數的inflate方法時,它會以ViewGroup對象不等於空的判斷值為參數attachToRoot的值,並和之前的參數傳到另一個帶有三個參數的inflate方法。
最終都會調用下面這個inflate方法。這里我們只需關注ViewGroup root、boolean attachToRoot和返回值result就可以了,所以我把其它省略了。
1 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { 2 synchronized (mConstructorArgs) { ......
9 View result = root; 10 11 try { ......
32 33 if (TAG_MERGE.equals(name)) { ......
40 } else { 41 // Temp is the root view that was found in the xml 42 final View temp = createViewFromTag(root, name, inflaterContext, attrs); 43 44 ViewGroup.LayoutParams params = null; 45 46 if (root != null) { ......
51 // Create layout params that match root, if supplied 52 params = root.generateLayoutParams(attrs); 53 if (!attachToRoot) { 54 // Set the layout params for temp if we are not 55 // attaching. (If we are, we use addView, below) 56 temp.setLayoutParams(params); 57 } 58 } ......
70 71 // We are supposed to attach all the views we found (int temp) 72 // to root. Do that now. 73 if (root != null && attachToRoot) { 74 root.addView(temp, params); 75 } 76 77 // Decide whether to return the root that was passed in or the 78 // top view found in xml. 79 if (root == null || !attachToRoot) { 80 result = temp; 81 } 82 } 83 84 }
......
102 return result; 103 } 104 }
在第9行可以看到返回值result的初始值是root。
然后整個方法就只有第79行的if語句會修改result。而條件就是root為null,或者attachToRoot為假。而這個temp在第42行可以找到初值。其實上面的注釋已經告訴我們,temp是我們傳入的布局文件中的根視圖。
在Adapter的getView方法里,雖然傳入的root不為null,但attachToRoot為false,所以返回值就是我們傳入的布局文件中的根視圖,用來為布局中的控件初始化和修改。而上面那種動態添加布局的方式,傳入的root為null,也是如此。
那為什么getView方法不直接傳進null呢?這個我沒能從源碼上找到為什么,只知道,當傳進ViewGroup對象為null時,運行會報錯,說Adapter為null,就是Adapter對象沒有初始化。那如果傳進了root,但令attachToRoot為true呢?也是會運行報錯。
先看看第73行的if語句。它表示的是root不為null,同時attachToRoot為true時,就會把temp和它的布局參數添加到root中。
所以在上面那種情況中,root其實就是ListView,而在ListView的父類AdapterView中,其方法的注釋聲明了是不支持addView方法的,一旦調用就會報錯。
而我上面動態添加布局的那種寫法,也就可以替換成:
View refreshView = layoutInflater.inflate(R.layout.push_to_refresh_header, linearLayout);
把父布局的根視圖傳進去就可以了,它會自動生成attachToRoot的值為true。然后再把要添加的布局中的根視圖添加到root中。