詳解LayoutInflater.inflate()

LayoutInflater.inflate()這個方法,大家一定很熟悉——在給fragment添加布局文件,或者在RecyclerView的Adapter中為item添加布局時,都會用到。inflate()這個方法需要最多三個參數:resource,root,以及attachToRoot。參考源碼,就知道了這裡的resource是你具體要添加的那個布局文件,root是布局的根參數,那麼attachToRoot是什麼意思呢?什麼時候為true,什麼時候為false?

三個參數的關係

參見官方文檔,對這三個參數的介紹是:被填充的層是否應該附在root參數內部?如果是false,root參數只適用於為xml根元素View創建正確的LayoutParams的子類。

什麼意思呢?就是說,如果attachToRoot為true,那麼resource指定的布局文件就會依附於root指定的ViewGroup,然後這個方法就會返回root,否則,只會將resource指定的布局文件填充並將其返回,具體可以參考源碼:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {nn synchronized (mConstructorArgs) {nn Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");nn final Context inflaterContext = mContext;nn final AttributeSet attrs = Xml.asAttributeSet(parser);nn Context lastContext = (Context) mConstructorArgs[0];nn mConstructorArgs[0] = inflaterContext;nn View result = root;nn try {nn // Look for the root node.nn int type;nn while ((type = parser.next()) != XmlPullParser.START_TAG &&nn type != XmlPullParser.END_DOCUMENT) {nn // Emptyn if (type != XmlPullParser.START_TAG) {nn throw new InflateException(parser.getPositionDescription()nn + ": No start tag found!");nn }nn final String name = parser.getName();nn nn if (DEBUG) {nn System.out.println("**************************");nn System.out.println("Creating root view: "nn + name);nn System.out.println("**************************");nn }nnnn if (TAG_MERGE.equals(name)) {nn if (root == null || !attachToRoot) {nn throw new InflateException("<merge /> can be used only with a valid "nn + "ViewGroup root and attachToRoot=true");nn }nnnn rInflate(parser, root, inflaterContext, attrs, false);nn } else {nn // Temp is the root view that was found in the xmlnn final View temp = createViewFromTag(root, name, inflaterContext, attrs);nnnn ViewGroup.LayoutParams params = null;nnnn if (root != null) {nn if (DEBUG) {nn System.out.println("Creating params from root: " +nn root);nn }nn // Create layout params that match root, if suppliednn params = root.generateLayoutParams(attrs);nn if (!attachToRoot) {nn // Set the layout params for temp if we are notnn // attaching. (If we are, we use addView, below)nn temp.setLayoutParams(params);nn }nn }nnnn if (DEBUG) {nn System.out.println("-----> start inflating children");nn }nnnn // Inflate all children under temp against its context.nn rInflateChildren(parser, temp, attrs, true);nnnn if (DEBUG) {nn System.out.println("-----> done inflating children");nn }nnnn // We are supposed to attach all the views we found (int temp)nn // to root. Do that now.nn if (root != null && attachToRoot) {nn root.addView(temp, params);nn }nnnn // Decide whether to return the root that was passed in or thenn // top view found in xml.nn if (root == null || !attachToRoot) {nn result = temp;nn }nn }nnnn } catch (XmlPullParserException e) {nn InflateException ex = new InflateException(e.getMessage());nn ex.initCause(e);nn throw ex;nn } catch (Exception e) {nn InflateException ex = new InflateException(nn parser.getPositionDescription()nn + ": " + e.getMessage());nn ex.initCause(e);nn throw ex;nn } finally {nn // Dont retain static reference on context.nn mConstructorArgs[0] = lastContext;nn mConstructorArgs[1] = null;nn }nnnn Trace.traceEnd(Trace.TRACE_TAG_VIEW);nnnn return result;nn }nn }n

總結一下,就是:

  • 若attachToRoot為true且root不為null,則調用root.addView()方法

  • 若root為null,或者attachToRoot為false,則直接將temp賦於result(temp是通過root構造的,result就是root)

何時為true,何時為false?

就拿我們的Adapter來說吧,在創建item布局時,有下列幾種情況:

  • inflate(R.layout.xxx,null);

  • inflate(R.layout.xxx,parent,false);

  • inflate(R.layout.xxx,parent,true);

那麼就講一下這三種情況把。

首先,inflate(R.layout.xxx,null) 。這是最簡單的寫法,這樣生成的布局就是根據R.layout.xxx返回的View。要知道,這個布局文件中的寬高屬性都是相當於父布局而言的。由於沒有指定parent,所以他的寬高屬性就失效了,因此不管你怎麼改寬高屬性,都無法按你想像的那樣顯示。

然後,inflate(R.layout.xxx,parent,false)。相較於前者,這裡加了父布局,不管後面是true還是false,由於有了parent,布局文件的寬高屬性是有依靠了,這時候顯示的寬高樣式就是布局文件中的那樣了。

最後,inflate(R.layout.xxx,parent,true)。這樣……等等,報錯了???哦,不要驚奇,分析一下原因:首先,有了parent,所以可以正確處理布局文件的寬高屬性。然後,既然attachToRoot為true,那麼根據上面的源碼就會知道,這裡會調用root的addView方法。而如果root是listView等,由於他們是繼承自AdapterView的,看看AdapterView的addView方法:

@Overridenn public void addView(View child) {nn throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");nn }n

不資磁啊,那好吧,如果換成RecyclerView呢?還是報錯了,看看源碼:

if (child.getParent() != null) {nn throw new IllegalStateException("The specified child already has a parent. " +nn "You must call removeView() on the childs parent first.");nn }n

現在知道了吧,adpater裡面不要用true。那麼什麼時候用true呢?答案是fragment。在為fragment創建布局時,如果為true,那麼這個布局文件就會被添加到父activity中盛放fragment的布局中。

推薦閱讀:

拿到一個apk包後,怎麼判斷其是否加殼了?是否做了代碼混淆?
Android 程序猿如何繼續深入的研究技術層的知識?請教各位前輩指條明路
為什麼要學習 Android 開發?
開發一個App需要什麼?
Android Studio編譯慢、卡死和狂佔內存怎麼破?

TAG:Android开发 | Android |