您好,登錄后才能下訂單哦!
前言
在平時的開發過程中,我們經常會用LayoutInflater這個類,比如說在Fragment$onCreateView
和RecyclerView.Adapter$onCreateViewHolder
中都會用到。它的用法也無非就是LayoutInflater.inflate(resourceId, root, attachToRoot)
,第一個參數沒什么好說的,但第二個和第三個參數結合起來會帶來一定的迷惑性。之前有時候會發現界面布局上出了一些問題,查了很久之后偶然的改動了這兩個參數,發現問題解決了,然后也就過去了,并沒有去思考這是為什么,然后下次可能又重復這種困境了。
所以想在這里總結一下,避免以后繼續掉坑。
先來看看inflate方法的注釋:
/** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy (if * <em>attachToRoot</em> is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if <em>attachToRoot</em> is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
首先需要了解的一點是,一個View的測量結果并不只是由它自己的layout_width和layout_height(即LayoutParams)所決定的,而是由父容器給它的約束(MeasureSpec)和它自身的LayoutParams共同決定的。
達成這個共識之后,我們再來看看它的參數。
root != null && attachToRoot
,返回的View就是傳進來的root,否則返回由布局文件所創建的View對象。用幾個例子來說明一下會比較好理解。Activity的布局是一個LinearLayout,要添加的布局如下:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item_text" android:layout_width="200dp" android:layout_height="50dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:background="@color/colorAccent" android:gravity="center" android:text="item: text"/>
正常的情況
// 第一種方法 View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, mLinearLayout, true); Log.d(TAG, "inflated view is " + inflatedView); // 第二種方法 View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, mLinearLayout, false); Log.d(TAG, "inflated view is " + inflatedView); mLinearLayout.addView(inflatedView);
視覺上的結果都是一樣的
但是Log就有一點不一樣了,這就是attachToRoot不同的值所導致的。
第一種方法的Log
D/MainActivity: inflated view is android.widget.LinearLayout{36e9aac V.E...... ......I. 0,0-0,0 #7f0c0051 app:id/linear}
第二種方法的Log
D/MainActivity: inflated view is android.support.v7.widget.AppCompatTextView{3c9d37b V.ED..... ......ID 0,0-0,0 #7f0c0054 app:id/item_text}
還有一個需要注意的地方是:如果在第一種方法的基礎上再加上mLinearLayout.addView(inflatedView)
就會造成報錯
IllegalStateException: The specified child already has a parent....
。
而如果第二種方法沒有這句話,界面上是看不到任何東西的。
root為null的情況
mLinearLayout = (LinearLayout) findViewById(R.id.linear); View inflatedView = LayoutInflater.from(this).inflate(R.layout.item_text, null); Log.d(TAG, "inflated view is " + inflatedView); mLinearLayout.addView(inflatedView);
此時再看看它的布局文件:
<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item_text" android:layout_width="200dp" android:layout_height="50dp" android:layout_marginLeft="16dp" android:layout_marginTop="16dp" android:background="@color/colorAccent" android:gravity="center" android:text="item: text"/>
不難發現,所有layout_xxx的屬性全都失效了。
RecyclerView中的Inflater
上面說了,在創建布局的時候,要把布局添加到root中去,并且有兩種方法,但是我們在onCreateViewHolder中添加布局的時候卻是這樣寫的:
@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text, parent, false); return new MyViewHolder(view); }
如果第三個參數傳了true還會報錯,這又是為什么呢?
java.lang.IllegalStateException: The specified child already has a parent.
直觀上來解釋就是,子View的添加與刪除是由RecyclerView來管理的,不需要我們來添加。但我們還是從RecyclerView的代碼來理解一下會好一些。
以LinearLayoutManager為例,RecyclerView在創建子View的時候會調用到LinearLayoutManager$layoutChunk
方法:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { // 在這里會調用到Adapter$onCreateViewHolder View view = layoutState.next(recycler); if (view == null) { if (DEBUG && layoutState.mScrapList == null) { throw new RuntimeException("received null view when unexpected"); } // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.mFinished = true; return; } LayoutParams params = (LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { addView(view, 0); } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView(view); } else { addDisappearingView(view, 0); } } // 省略其它大部分代碼 }
在初始化的時候,View view = layoutState.next(recycler)
里面會調用到我們熟悉的onCreateViewHolder
方法,然后我們在里面inflate的過程中第三個參數傳了true,將子View添加到了RecyclerView中去了。然而,獲得View之后,調用到了addView(因為是初始化,不可能調用addDisappearingView)
,這里又會去添加一次,所以報出了上面的IllegalStateException
異常。
總結
以上就是這篇文章的全部內容了,希望本文的內容對各位Android開發者們能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。