Android LayoutInflater.inflate()詳解及分析
Android LayoutInflater.inflate()詳解
深入理解LayoutInflater.inflate()
由于我們很容易習(xí)慣公式化的預(yù)置代碼,有時(shí)我們會忽略很優(yōu)雅的細(xì)節(jié)。LayoutInflater以及它在Fragment的onCreateView()中填充View的方式帶給我的就是這樣的感受。這個(gè)類用于將XML文件轉(zhuǎn)換成相對應(yīng)的ViewGroup和控件Widget。我嘗試在Google官方文檔與網(wǎng)絡(luò)上其他討論中尋找有關(guān)的說明,而后發(fā)現(xiàn)許多人不但不清楚LayoutInflater的inflate()方法的細(xì)節(jié),而且甚至在誤用它。
這里的困惑很大程度上是因?yàn)镚oogle上有關(guān)attachToRoot(也就是inflate()方法第三個(gè)參數(shù))的文檔太模糊:
被填充的層是否應(yīng)該附在root參數(shù)內(nèi)部?如果是false,root參數(shù)只適用于為XML根元素View創(chuàng)建正確的LayoutParams的子類。
其實(shí)意思就是:如果attachToRoot是true的話,那第一個(gè)參數(shù)的layout文件就會被填充并附加在第二個(gè)參數(shù)所指定的ViewGroup內(nèi)。方法返回結(jié)合后的View,根元素是第二個(gè)參數(shù)ViewGroup。如果是false的話,第一個(gè)參數(shù)所指定的layout文件會被填充并作為View返回。這個(gè)View的根元素就是layout文件的根元素。不管是true還是false,都需要ViewGroup的LayoutParams來正確的測量與放置layout文件所產(chǎn)生的View對象。
attachToRoot傳入true代表layout文件填充的View會被直接添加進(jìn)ViewGroup,而傳入false則代表創(chuàng)建的View會以其他方式被添加進(jìn)ViewGroup。
讓我們就兩種情況多舉一些例子來更深入的理解。
attachToRoot是True
假設(shè)我們在XML layout文件中寫了一個(gè)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>
現(xiàn)在我們想動態(tài)地把這個(gè)按鈕添加進(jìn)Fragment或Activity的LinearLayout中。如果這里L(fēng)inearLayout已經(jīng)是一個(gè)成員變量mLinearLayout了,我們只需要通過如下代碼達(dá)成目標(biāo):
inflater.inflate(R.layout.custom_button, mLinearLayout, true);
我們指定了用于填充button的layout資源文件,然后我們告訴LayoutInflater我們想把button添加到mLinearLayout中。這里Button的LayoutParams種類為LinearLayout.LayoutParams。
下面的代碼也有同樣的效果。LayoutInflater的兩個(gè)參數(shù)的inflate()方法自動將attachToRoot設(shè)置為true。
inflater.inflate(R.layout.custom_button, mLinearLayout);
另一種在attachToRoot中傳遞true的情況是使用自定義View。我們看一個(gè)layout文件中根元素有標(biāo)簽的例子。標(biāo)簽標(biāo)識著這個(gè)layout文件的根ViewGroup可以有多種類型。
public class MyCustomView extends LinearLayout { ... private void init() { LayoutInflater inflater = LayoutInflater.from(getContext()); inflater.inflate(R.layout.view_with_merge_tag, this); } }
這就是一個(gè)很好的使用attachToRoot的例子。這個(gè)例子中l(wèi)ayout文件沒有ViewGroup作為根元素,所以我們指定我們自定義的LinearLayout作為根元素。如果layout文件有一個(gè)FrameLayout作為根元素而不是,那么FrameLayout和它的子元素都可以正常填充,而后都會被添加到LinearLayout中,LinearLayout是根ViewGroup,包含著FrameLayout和其子元素。
attachToRoot是False
我們看一下什么時(shí)候attachToRoot應(yīng)該是false。在這種情況下,inflate()方法中的第一個(gè)參數(shù)所指定的View不會被添加到第二個(gè)參數(shù)所指定的ViewGroup中。
回憶一下剛才的例子中的Button,我們想通過layout文件添加自定義的Button至mLinearLayout中。當(dāng)attachToRoot為false時(shí),我們?nèi)钥梢詫utton添加到mLinearLayout中,但是這需要我們自己動手。
Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false); mLinearLayout.addView(button);
這兩行代碼與剛才attachToRoot為true時(shí)的一行代碼等效。通過傳入false,我們告訴LayoutInflater我們不暫時(shí)還想將View添加到根元素ViewGroup中,意思是我們一會兒再添加。在這個(gè)例子中,一會兒再添加就是在inflate()后調(diào)用addView()方法。
在將attachToRoot設(shè)置為false的例子中,由于要手動添加View進(jìn)ViewGroup導(dǎo)致代碼變多了。將Button添加到LinearLayout中還是用一行代碼直接將attachToRoot設(shè)置為true簡便一些。下面我們看一下什么情況下attachToRoot必須傳入false。
每一個(gè)RecyclerView的子元素都要在attachToRoot設(shè)置為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負(fù)責(zé)決定什么時(shí)候展示它的子View,這個(gè)不由我們決定。在任何我們不負(fù)責(zé)將View添加進(jìn)ViewGroup的情況下都應(yīng)該將attachToRoot設(shè)置為false。
當(dāng)在Fragment的onCreateView()方法中填充并返回View時(shí),要將attachToRoot設(shè)為false。如果傳入true,會拋出IllegalStateException,因?yàn)橹付ǖ淖覸iew已經(jīng)有父View了。你需要指定在哪里將Fragment的View放進(jìn)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()方法中的第二個(gè)參數(shù)被傳入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添加進(jìn)ViewGroup,為什么還要傳入ViewGroup呢?為什么inflate()方法必須要傳入根ViewGroup?
原因是及時(shí)不需要馬上將新填充的View添加進(jìn)ViewGroup,我們還是需要這個(gè)父元素的LayoutParams來在將來添加時(shí)決定View的size和position。
你在網(wǎng)上一定會遇到一些不正確的建議。有些人會建議你如果將attachToRoot設(shè)置為false的話直接將根ViewGroup傳入null。但是,如果有父元素的話,還是應(yīng)該傳入的。
Lint會警告你不要講null作為root傳入。你的App不會掛掉,但是可能會表現(xiàn)異常。當(dāng)你的子View沒有正確的LayoutParams時(shí),它會自己通過generateDefaultLayoutParams)計(jì)算。
你可能并不想要這些默認(rèn)的LayoutParams。你在XML指定的LayoutParams會被忽略。我們可能已經(jīng)指定了子View要填充父元素的寬度,但父View又wrap_content導(dǎo)致最終的View小很多。
下面是一種沒有ViewGroup作為root傳入inflate()方法的情況。當(dāng)為AlertDialog創(chuàng)建自定義View時(shí),還無法訪問父元素。
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傳入。后來我發(fā)現(xiàn)AlertDialog還是會重寫LayoutParams并設(shè)置各項(xiàng)參數(shù)為match_parent。但是,規(guī)則還是在有ViewGroup可以傳入時(shí)傳入它。
避開崩潰、異常表現(xiàn)與誤解
希望這篇文章可以幫助你在使用LayoutInflater時(shí)避開崩潰、異常表現(xiàn)與誤解。下面整理了文章的要點(diǎn):
- 如果可以傳入ViewGroup作為根元素,那就傳入它。
- 避免將null作為根ViewGroup傳入。
- 當(dāng)我們不負(fù)責(zé)將layout文件的View添加進(jìn)ViewGroup時(shí)設(shè)置attachToRoot參數(shù)為false。
- 不要在View已經(jīng)被添加進(jìn)ViewGroup時(shí)傳入true。
- 自定義View時(shí)很適合將attachToRoot設(shè)置為true。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
- Android開發(fā)實(shí)現(xiàn)自定義Toast、LayoutInflater使用其他布局示例
- Android中LayoutInflater.inflater()的正確打開方式
- Android 中LayoutInflater.inflate()方法的介紹
- Android中使用LayoutInflater要注意的一些坑
- Android布局加載之LayoutInflater示例詳解
- Android LayoutInflater加載布局詳解及實(shí)例代碼
- Android LayoutInflater深入分析及應(yīng)用
- Android 老生常談LayoutInflater的新認(rèn)知
相關(guān)文章
Android自定義View實(shí)現(xiàn)APP啟動頁倒計(jì)時(shí)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)APP啟動頁倒計(jì)時(shí)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Android studio 出現(xiàn) Unsupported major.minor version 52.0解決辦法
這篇文章主要介紹了Android studio 出現(xiàn) Unsupported major.minor version 52.0解決辦法的相關(guān)資料,需要的朋友可以參考下2016-12-12listview 選中高亮顯示實(shí)現(xiàn)方法
當(dāng)點(diǎn)擊左側(cè)ListView后,選中的一行就會一直呈高亮狀態(tài)顯示,圖中選中行字的顏色顯示為藍(lán)色(注意:是選中行后一直高亮,而不是只是點(diǎn)擊時(shí)高亮),如果再次點(diǎn)擊另外的一行, 則新的那一行就高亮,下面就來實(shí)現(xiàn)這個(gè)高亮效果的顯示2012-11-11Android編程實(shí)現(xiàn)列表側(cè)滑刪除的方法詳解
這篇文章主要介紹了Android編程實(shí)現(xiàn)列表側(cè)滑刪除的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android列表側(cè)滑刪除功能的原理與具體實(shí)現(xiàn)技巧,注釋中包含詳盡的說明,需要的朋友可以參考下2018-01-01Android開發(fā)之時(shí)間日期操作實(shí)例
這篇文章主要介紹了Android開發(fā)之時(shí)間日期操作,是Android程序開發(fā)中常見的一個(gè)功能,需要的朋友可以參考下2014-08-08Android使用AlertDialog實(shí)現(xiàn)的信息列表單選、多選對話框功能
在使用AlertDialog實(shí)現(xiàn)單選和多選對話框時(shí),分別設(shè)置setSingleChoiceItems()和setMultiChoiceItems()函數(shù)。具體實(shí)現(xiàn)代碼大家參考下本文吧2017-03-03Android開發(fā)實(shí)現(xiàn)橫向列表GridView橫向滾動的方法【附源碼下載】
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)橫向列表GridView橫向滾動的方法,結(jié)合實(shí)例形式分析了Android橫向列表GridView實(shí)現(xiàn)橫向滾動的相關(guān)布局與功能實(shí)現(xiàn)技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2018-01-01