Android中深入理解 LayoutInflater.inflate()
由於我們很容易習慣公式化的預置程式碼,有時我們會忽略很優雅的細節。LayoutInflater以及它在Fragment的onCreateView()中填充View的方式帶給我的就是這樣的感受。這個類用於將XML檔案轉換成相對應的ViewGroup和控制元件Widget。我嘗試在Google官方文件與網路上其他討論中尋找有關的說明,而後發現許多人不但不清楚LayoutInflater的inflate()方法的細節,而且甚至在誤用它。
這裡的困惑很大程度上是因為Google上有關attachToRoot(也就是inflate()方法第三個引數)的文件太模糊:
被填充的層是否應該附在root引數內部?如果是false,root引數只是用於為XML根元素View建立正確的LayoutParams的子類。
其實意思就是:如果attachToRoot是true的話,那第一個引數的layout檔案就會被填充並附加在第二個引數所指定的ViewGroup內。方法返回結合後的View,根元素是第二個引數ViewGroup。如果是false的話,第一個引數所指定的layout檔案會被填充並作為View返回。這個View的根元素就是layout檔案的根元素。不管是true還是false,都需要ViewGroup的LayoutParams來正確的測量與放置layout檔案所產生的View物件。
attachToRoot傳入true代表layout檔案填充的View會被直接新增進ViewGroup,而傳入false則代表建立的View會以其他方式被新增進ViewGroup。
讓我們就兩種情況多舉一些例子來更深入的理解。
attachToRoot是True
假設我們在XML layout檔案中寫了一個Button並指定了寬高為match_parent。
<Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/custom_button"> </Button>
現在我們想動態地把這個按鈕新增進Fragment或Activity的LinearLayout中。如果這裡LinearLayout已經是一個成員變數mLinearLayout了,我們只需要通過如下程式碼達成目標:
inflater.inflate(R.layout.custom_button, mLinearLayout, true);
我們指定了用於填充button的layout資原始檔,然後我們告訴LayoutInflater我們想把button新增到mLinearLayout中。這裡Button的LayoutParams種類為LinearLayout.LayoutParams。
下面的程式碼也有同樣的效果。LayoutInflater的兩個引數的inflate()方法自動將attachToRoot設定為true。
inflater.inflate(R.layout.custom_button, mLinearLayout);
另一種在attachToRoot中傳遞true的情況是使用自定義View。我們看一個layout檔案中根元素有標籤的例子。標籤標識著這個layout檔案的根ViewGroup可以有多種型別。
public class MyCustomView extends LinearLayout { ... private void init() { LayoutInflater inflater = LayoutInflater.from(getContext()); inflater.inflate(R.layout.view_with_merge_tag, this); } }
這就是一個很好的使用attachToRoot的例子。這個例子中layout檔案沒有ViewGroup作為根元素,所以我們指定我們自定義的LinearLayout作為根元素。如果layout檔案有一個FrameLayout作為根元素而不是,那麼FrameLayout和它的子元素都可以正常填充,而後都會被新增到LinearLayout中,LinearLayout是根ViewGroup,包含著FrameLayout和其子元素。
attachToRoot是False
我們看一下什麼時候attachToRoot應該是false。在這種情況下,inflate()方法中的第一個引數所指定的View不會被新增到第二個引數所指定的ViewGroup中。
回憶一下剛才的例子中的Button,我們想通過layout檔案新增自定義的Button至mLinearLayout中。當attachToRoot為false時,我們仍可以將Button新增到mLinearLayout中,但是這需要我們自己動手。
Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false); mLinearLayout.addView(button);
這兩行程式碼與剛才attachToRoot為true時的一行程式碼等效。通過傳入false,我們告訴LayoutInflater我們不暫時還想將View新增到根元素ViewGroup中,意思是我們一會兒再新增。在這個例子中,一會兒再新增就是在inflate()後呼叫addView()方法。
在將attachToRoot設定為false的例子中,由於要手動新增View進ViewGroup導致程式碼變多了。將Button新增到LinearLayout中還是用一行程式碼直接將attachToRoot設定為true簡便一些。下面我們看一下什麼情況下attachToRoot必須傳入false。
每一個RecyclerView的子元素都要在attachToRoot設定為false的情況下填充。這裡子View在onCreateViewHolder()中填充。
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(getActivity()); View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false); return new ViewHolder(view); }
RecyclerView負責決定什麼時候展示它的子View,這個不由我們決定。在任何我們不負責將View新增進ViewGroup的情況下都應該將attachToRoot設定為false。
當在Fragment的onCreateView()方法中填充並返回View時,要將attachToRoot設為false。如果傳入true,會丟擲IllegalStateException,因為指定的子View已經有父View了。你需要指定在哪裡將Fragment的View放進Activity裡,而新增、移除或替換Fragment則是FragmentManager的事情。
FragmentManager fragmentManager = getSupportFragmentManager(); Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup); if (fragment == null) { fragment = new MainFragment(); fragmentManager.beginTransaction().add(R.id.root_viewGroup, fragment).commit(); }
上面程式碼中root_viewGroup就是Activity中用於放置Fragment的容器,它會作為inflate()方法中的第二個引數被傳入onCreateView()中。它也是你在inflate()方法中傳入的ViewGroup。FragmentManager會將Fragment的View新增到ViewGroup中,你可不想新增兩次。
public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false); … return view; }
問題是:如果我們不需在onCreateView()中將View新增進ViewGroup,為什麼還要傳入ViewGroup呢?為什麼inflate()方法必須要傳入根ViewGroup?
原因是及時不需要馬上將新填充的View新增進ViewGroup,我們還是需要這個父元素的LayoutParams來在將來新增時決定View的size和position。
你在網上一定會遇到一些不正確的建議。有些人會建議你如果將attachToRoot設定為false的話直接將根ViewGroup傳入null。但是,如果有父元素的話,還是應該傳入的。
Lint會警告你不要講null作為root傳入。你的App不會掛掉,但是可能會表現異常。當你的子View沒有正確的LayoutParams時,它會自己通過generateDefaultLayoutParams)計算。
你可能並不想要這些預設的LayoutParams。你在XML指定的LayoutParams會被忽略。我們可能已經指定了子View要填充父元素的寬度,但父View又wrap_content導致最終的View小很多。
下面是一種沒有ViewGroup作為root傳入inflate()方法的情況。當為AlertDialog建立自定義View時,還無法訪問父元素。
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext); View customView = inflater.inflate(R.layout.custom_alert_dialog, null); ... dialogBuilder.setView(customView); dialogBuilder.show();
在這種情況下,可以將null作為root ViewGroup傳入。後來我發現AlertDialog還是會重寫LayoutParams並設定各項引數為match_parent。但是,規則還是在有ViewGroup可以傳入時傳入它。
避開崩潰、異常表現與誤解
希望這篇文章可以幫助你在使用LayoutInflater時避開崩潰、異常表現與誤解。下面整理了文章的要點:
- 如果可以傳入ViewGroup作為根元素,那就傳入它。
- 避免將null作為根ViewGroup傳入。
- 當我們不負責將layout檔案的View新增進ViewGroup時設定attachToRoot引數為false。
- 不要在View已經被新增進ViewGroup時傳入true。
- 自定義View時很適合將attachToRoot設定為true。
相關文章
- 深入理解LayoutInflater.inflate()
- 深入理解 Android 中的 MatrixAndroid
- 深入理解Android中的SharedPreferencesAndroid
- 深入理解Android中的ClassLoaderAndroid
- 深入理解AndroidAndroid
- Android 深入理解Android中的自定義屬性Android
- 深入理解 Android 中的各種 ContextAndroidContext
- 帶你深入理解Android中的自定義屬性!!!Android
- Android LayoutInflater.inflate各個引數作Android
- Android 深入理解 Notification 機制Android
- [深入理解Android卷二 全文-第五章]深入理解PowerManagerServiceAndroid
- JS中this的深入理解JS
- 深入理解Js中的thisJS
- android中onMeasure初看,深入理解佈局之一!Android
- [深入理解Android卷二 全文-第六章]深入理解ActivityManagerServiceAndroid
- [深入理解Android卷二 全文-第四章]深入理解PackageManagerServiceAndroidPackage
- [深入理解Android卷二 全文-第三章]深入理解SystemServerAndroidServer
- [深入理解Android卷一全文-第十章]深入理解MediaScannerAndroid
- 深入理解Android逆向除錯原理Android除錯
- 深入理解Android訊息機制Android
- 深入理解Android中的快取機制(三)磁碟快取Android快取
- 深入理解Java中的鎖Java
- 深入理解 Java 中的 LambdaJava
- 深入理解Java中的AQSJavaAQS
- 深入理解 JavaScript 中的 classJavaScript
- 深入理解Oracle中的DBCAOracle
- 深入理解Oracle中的MutexOracleMutex
- 深入理解Oracle中的latchOracle
- 深入理解Android中的快取機制(一)快取簡介Android快取
- [深入理解Android卷一全文-第八章]深入理解Surface系統Android
- [深入理解Android卷一全文-第七章]深入理解Audio系統Android
- 深入理解Android 之 Activity啟動流程(Android 10)Android
- 深入理解 Android 訊息機制原理Android
- 深入理解 Java 中 SPI 機制Java
- 深入理解Java中的逃逸分析Java
- 深入理解Java中的鎖(一)Java
- 深入理解Java中的鎖(二)Java
- 深入理解Java中的Garbage CollectionJava