1.引言
在 Android
中,我們需要?jiǎng)討B(tài)添加View
的時(shí)候历葛,通常會(huì)先去加載布局恤溶,那加載布局的方式一般有以下兩種方式:
//第一種方式
View.inflate(this,R.layout.xx,null);
//第二種方式
LayoutInflater.from(this).inflate(R.layout.xx,null,false);
一般有經(jīng)驗(yàn)的人會(huì)跟你說,用第二種方式靠譜宏娄,那如果你偏偏要用第一種呢孵坚,也不是不可以卖宠,用不好的話扛伍,就會(huì)出現(xiàn)一些奇奇怪怪的事:
2. 怪事一
我現(xiàn)在有一個(gè) Activity
類的布局刺洒,里面只有一個(gè)LInearLayout
,我需要?jiǎng)討B(tài)往這個(gè)LinearLayout
中添加一個(gè) TextView
, 這個(gè)TextView
我設(shè)置了高度為 100dp
, 背景顏色設(shè)置為 紅色逆航,直接看下面代碼:
//activity_activity_inflate
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/ll_container"
android:orientation="vertical" />
___________________________________________________________
//item_child
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#ff5232"
android:text="Hello world"
android:textColor="#ffffff"
android:layout_width="match_parent"
android:id="@+id/tv"
android:layout_height="100dp" />
動(dòng)態(tài)添加 TextView
LinearLayout ll_container = findViewById(R.id.ll_container);
View child = View.inflate(this,R.layout.item_child,null);
ll_container.addView(child);
運(yùn)行結(jié)果圖:
怪事來了因俐,??我們明明設(shè)置 TextView 的高度為 100dp , 怎么就顯示 wrap_content 呢?這里我們記錄一下抹剩,為怪事1.
3.怪事二
我們修改一下我們動(dòng)態(tài)添加的代碼:
LinearLayout ll_container = findViewById(R.id.ll_container);
View child = View.inflate(this,R.layout.item_child,ll_container);
ll_container.addView(child);
第三個(gè)參數(shù)把 null 修改為 ll_container , 再次運(yùn)行:
oh..mygod,程序直接崩潰了蛉艾,錯(cuò)誤日志信息如下:
The specified child already has a parent. You must call removeView() on the child's parent first.
當(dāng)然在我們使用RecyclerView
的 adapter
里伺通,也會(huì)要加載布局罐监,使用不當(dāng),也是會(huì)出現(xiàn)所寫非所得的效果沟堡,那到底是怎么回事呢?
這里假設(shè)你對(duì)布局加載的流程有一定了解禀横,無論是哪種布局加載方式柏锄,最終都會(huì)調(diào)用到這里:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
LayoutInflater#inflate
方法趾娃,三個(gè)參數(shù)的抬闷。繼續(xù)跟進(jìn)的話就是 inflate
方法笤成,以下這個(gè)方法是我們重點(diǎn)關(guān)注的眷茁,我只留下重要代碼:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
....
View result = root;//-------------------1
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 (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
...
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {//---------2
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {//---------3
root.addView(temp, params);
}
if (root == null || !attachToRoot) {//---------4
result = temp;
}
}
return result;
}
注釋1:將root
賦值為result
, 用于返回的喊崖;
注釋2:root!=null
,就會(huì)解析我們?cè)?xml
中設(shè)置的屬性值轉(zhuǎn)換為 :params
, 同時(shí) attachToRoot
如果為 false
的話荤懂,就可以進(jìn)入if
條件里节仿,就可以為我們從xml
解析出來的temp
設(shè)置LayoutParams
了廊宪。
PS:這里需要主要的是箭启,我們通過 final View temp = createViewFromTag(root, name, inflaterContext, attrs)
; 這句話只是得到一個(gè) View
,如果xml
中寫的TextView
解析出來就是一個(gè) TextView
為 temp
,沒有攜帶 寬高傅寡,邊距等信息荐操。
注釋3:root!=null && attachToRoot(true)
,我們xml
中解析出的View
托启,會(huì)自動(dòng)幫我們add
到 root
里,并且是攜帶LayoutParams
.
注釋4:root==null || !attachToRoot
拐迁,把 result
修改為 temp
, xml
解析出來的 ,注意這時(shí)候是沒有任何 LayoutParams
的唠亚,這也就意味著我們?cè)?code>xml 中設(shè)置的屬性值會(huì)失效。
??回顧一下工窍,我們之前遇到的問題患雏。
我們通過:
LinearLayout ll_container = findViewById(R.id.ll_container);
View child = View.inflate(this,R.layout.item_child,null);
ll_container.addView(child);
這種方式addView
. 對(duì)應(yīng)到源碼里是怎么樣的呢淹仑?
跟進(jìn)去層層調(diào)用就到了這里:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
我們傳入的第三個(gè)參數(shù)為null
,就意味著:
return inflate(resource, null, false);
來重新對(duì)應(yīng)一下匀借,inflate 的三個(gè)參數(shù):
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
也就是意味著:root == null ,attachToRoot = false
.
換一種寫法:
View.inflate(this,R.layout.item_child,null);
等價(jià)于
設(shè)置了:root == null ,attachToRoot = false
根據(jù)我們前面的分析吓肋,只有root!=null
的時(shí)候是鬼,才會(huì)去給 params
賦值均蜜。
if (root != null) {
params = root.generateLayoutParams(attrs);
...
}
看到了吧囤耳,接下來就是 進(jìn)入注釋4那個(gè)代碼了:
if (root == null || !attachToRoot) {//---------4
result = temp;
}
將temp
返回了紫皇,這個(gè) temp
是啥聪铺,就是我們從xml
中解析出的View
(沒有任何params
).
?? 第二個(gè)問題,為什么我們那么寫崩潰報(bào)錯(cuò)撒桨,把之前的拿來再看一下:
LinearLayout ll_container = findViewById(R.id.ll_container);
View child = View.inflate(this,R.layout.item_child,ll_container);
ll_container.addView(child);
與第一次不同的是,我傳入了 ll_container
, 就意味著什么普气?意味著我們的 root!=null
&&
attachToRoot
為true
了现诀。
這樣的話坐桩,就進(jìn)入了我們注釋3處 的代碼了:
if (root != null && attachToRoot) {//---------3
root.addView(temp, params);
}
root
為ll_container
, 系統(tǒng)自動(dòng)幫我們 addView
添加進(jìn)去了绵跷,
root.addView(temp, params)
; 等價(jià)于ll_container.addView(temp,params)
;
return root
了碾局。
我們需要注意的是:
View child = View.inflate(this,R.layout.item_child,ll_container);
這句話本身并不會(huì)讓程序崩潰擦俐,真正讓程序崩潰的是:
ll_container.addView(child);
根據(jù)前面的分析蚯瞧,此時(shí)我們的 child
已經(jīng)被添加進(jìn)這個(gè)ll_container
里了埋合,你如果再把 child
添加到另外一個(gè)容器里,系統(tǒng)是不允許的萄传,一個(gè) child
只能有一個(gè) paraent
.
所以當(dāng)你執(zhí)行那句代碼的時(shí)候甚颂,系統(tǒng)這里有判斷:
看到了吧蜜猾,所以我們想把
child
添加到 ll_container
中,只需要:
View.inflate(this,R.layout.item_child,ll_container);
寫這一行足矣振诬,下面那行就屬于畫蛇添足了蹭睡。
好了,今天分享就到此結(jié)束了赶么,有問題肩豁,評(píng)論區(qū)見~