標籤:
概述
在Android開發中LayoutInflater的應用非常普遍,可以將res/layout/下的xml布局檔案,執行個體化為一個View或者ViewGroup的控制項。與findViewById的作用類似,但是findViewById在xml布局檔案中尋找具體的控制項,兩者並不完全相同。
應用情境:
1.在一個沒有載入或者想要動態載入的介面中,需要使用layoutInflater.inflate()來載入布局檔案;
2.對於一個已經載入的介面,就可以使用findViewById方法來獲得其中的介面元素;
獲得LayoutInflater的對象三種實現方式
1.LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);2.LayoutInflater layoutInflater = getLayoutInflater();3.LayoutInflater layoutInflater = LayoutInflater.from(context);
然而,上面三種LayoutInflater的本質是相同的,最終都是調用第一種的方式產生layoutInflater對象,我們可以查看源碼:
getLayoutInflater()的源碼:
Activity的getLayoutInflater()方法是調用PhoneWindow 的getLayoutInflater()方法,源碼如下:
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); }
LayoutInflater.from(context)的源碼:
/** * Obtains the LayoutInflater from the given context. */ public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
可以看出其實也都是調用context.getSystemService()。得到了LayoutInflater的對象之後就可以調用它的inflate()方法來載入布局了。
inflate方法
inflate方法根據傳入不同的參數,一共有四種方法,如下:
public View inflate(int resource, ViewGroup root)public View inflate(int resource, ViewGroup root, boolean attachToRoot)public View inflate(XmlPullParser parser, ViewGroup root)public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
這裡我們主要使用的是前面兩種方法。
我們來分析第一個方法public View inflate(int resource, ViewGroup root):
第一個參數表示的是要載入的布局id,第二個參數表示的是給載入的布局嵌套的父布局,如果不需要就直接傳null。
第二個方法public View inflate(int resource, ViewGroup root, boolean attachToRoot):
前兩個參數與上面的方法一致,第三個參數attachToRoot表示是否將載入的布局,添加到root中來。
從上面來看 inflate(layoutId, null) 和 inflate(R.layout.button, root,false) 效果似乎是一樣的,實際上兩種方法的效果完全不同。
程式碼範例
布局檔案button.xml:
<?xml version="1.0" encoding="utf-8"?><Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="100dp" android:text="button" ></Button>
Activity中的代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_other); ViewGroup group = (ViewGroup) findViewById(android.R.id.content); LayoutInflater layoutInflater = getLayoutInflater(); View view = layoutInflater.inflate(R.layout.button, null); group.addView(view); }}
如下:
修改Activity中的代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_other); ViewGroup group = (ViewGroup) findViewById(android.R.id.content); LayoutInflater layoutInflater = getLayoutInflater(); View view = layoutInflater.inflate(R.layout.button, group,false); group.addView(view); }}
運行後,:
繼續修改Activity中代碼:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_other); ViewGroup group = (ViewGroup) findViewById(android.R.id.content); LayoutInflater layoutInflater = getLayoutInflater(); View view = layoutInflater.inflate(R.layout.button, group,true); group.addView(view); }}
運行後,發現拋出異常資訊:
E/AndroidRuntime(3362): Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.
上面主要是使用了三種不同的inflate方法:
1.inflate(R.layout.button, null);
2.inflate(R.layout.button, group,false);
3.inflate(R.layout.button, group,true);
從上面三個不同的結果中,可以看到使用 inflate(layoutId, null)時,布局中的寬高屬性的設定無效,但是使用inflate(R.layout.button, root,false)時,布局中的寬高屬性又發揮了作用了。使用inflate(layoutId, root, true )時,又拋出異常,無法正常運行。
為什會出現這樣的結果呢?我們可以通過源碼來查看原因。
源碼解析
我們進入inflate方法中查看:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
發現上面三種方式,最終都是調用inflate(parser, root, attachToRoot)的方法,繼續進入:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, attrs, false, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false); ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 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 rInflate(parser, temp, attrs, true, true); // 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; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } finally { // Don‘t retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; } }
上面的代碼中,刪去了一個DEBUG的部分,可以一起看關鍵代碼:
1.View result = root,將參數root賦值到result上,並且方法結束時,返回result。
2.下面 final View temp = createViewFromTag(root, name, attrs, false); 建立一個View;
3.接著:
if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } }
如果root 不為空白,並且attachToRoot = false時,給temp設定setLayoutParams;這裡就解釋了上面代碼中使用inflate(R.layout.button, group,false)時,布局中的寬高能夠發生作用。
4.接著看下面:
if (root != null && attachToRoot) { root.addView(temp, params);}
如果root 不為空白,並且attachToRoot 為true時,這時params == null。從這裡,我們又可以看到為什上面使用inflate(R.layout.button, group,true)時,會拋出異常資訊,原因是在方法內部已經執行了root.addView(temp, params)的操作,外面在執行group.addView(view) 的代碼時,會拋出異常。解決該問題,只需要去掉group.addView(view)這段代碼即可。
5.繼續:
if (root == null || !attachToRoot) { result = temp;}
可以看到,當root的等於null,或者attachToRoot 為false時,直接將temp賦值給result。從這裡我們可以看到,在我們執行代碼inflate(R.layout.button, null)時,其實就是執行的是inflate(R.layout.button, null,false),result並沒有被設定setLayoutParams,所以在布局中設定的寬高屬性無效。
總結
inflate(layoutId, null)時,布局中的寬高屬性的設定無效;
inflate(R.layout.button, root,false)時,布局中的寬高屬性有效;
inflate(layoutId, root, true )時,後面不需要執行addView的操作,否則報異常。
以上針對layoutInflater.inflate的方法的解析便結束了。
Android 中LayoutInflater原理分析