1. 作用
官方釋義
Inflate a new view hierarchy from the specified xml resource
大概意思就是從給定的xml中加載view樹。
2. 用法
2.1 四種重載
1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root);
2. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
3. public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);
對外暴露的有四種重載,但是另外兩種需要自行傳入XmlPullParser,也就是說需要自行解析xml dom,屬於高端操作,一般使用較少。
其中,方法1也是使用了方法2,只不過在root非空的情況下,默認attachToRoot=true,故本文主要對方法2進行分析。
2.2 三個參數一個返回值
2.2.1 resource
resource顧名思義,資源,從給定的註解來看,要求爲layout資源。故參數一般爲R.layout.my_layout。
2.2.2 root
root顧名思義,根佈局,這裏的根佈局可以爲空。
當根部局爲空時,attachToRoot不生效且生成的view樹根節點的LayoutParams爲空,即你在layout中定義的根部局的什麼寬啊高啊內邊距外邊距相對位置等一切以layout_爲前綴的屬性都會失效。爲什麼呢?我們來看看LayoutParam的定義:
LayoutParams are used by views to tell their parents how they want to be laid out
意思是,LayoutParams是被用來告訴父佈局他想要怎麼被擺放的。
如果給定的root爲空,那麼加載出來的view就沒有父佈局,當然也沒必要擁有LayoutParams了!
2.2.3 attachToRoot
attachToRoot,即是否要將創建的view樹附加(連接)到給定的根佈局下。
2.2.4 返回值View
當不附加到根部局下時,返回的爲給定layout的根節點。
當附加到根部局下時,返回的爲根部局。
2.3 使用案例
所謂實踐出真知,下面來看一組栗子。
首先是一個根佈局,我們叫他root_layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fl_root"
android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_red_light">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:layout_gravity="center" />
</FrameLayout>
接着是一個我們想要加載的佈局inflate_layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_inflate_root"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/holo_blue_light">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="im inflated" />
</LinearLayout>
之後我們找個Activity簡單實驗一下,這裏只給出了onCreate方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
// activity content部分的根部局,實際上也是使用了LayoutInflater.inflate
// 將佈局加載到了contentParent下,感興趣的可以看下PhoneWindow下的實現
setContentView(R.layout.root_layout);
FrameLayout rootView = findViewById(R.id.fl_root); // 首先拿到root_layout的view樹根
// 之後我們對inflateView進行測試,對應下面三種情況
View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, true);
// View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView, false);
// View inflateView = LayoutInflater.from(this).inflate(R.layout.inflate_layout, rootView);
ViewGroup.LayoutParams rootLayoutParams = rootView.getLayoutParams();
ViewGroup.LayoutParams inflateLayoutParams = inflateView.getLayoutParams();
Log.d(TAG, "rootView=" + rootView + " rootView LayoutParams - " + rootLayoutParams);
Log.d(TAG, "inflateView=" + inflateView + "inflateView LayoutParams -" + inflateLayoutParams);
}
2.3.1 根佈局非空
2.3.3.1 attachToRoot=true
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@16527c6
當attachToRoot爲true時,返回的爲root根節點。且將我們的inflate_layout解析出的view樹連接到了給的root下。
2.3.3.1 attachToRoot=false
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -android.widget.FrameLayout$LayoutParams@79282b4
當attachToRoot爲false時,返回的爲inflate_layout的根view,且根據給定的rootView生成了自己的LayoutParam。但view並未被插入到給定根節點的view樹中。
2.3.1 根佈局爲空
D/MainActivity: rootView=android.widget.FrameLayout{6f919a1 V.E...... ......ID 0,0-0,0 #7f07003c app:id/fl_root} rootView LayoutParams - android.widget.FrameLayout$LayoutParams@16527c6
D/MainActivity: inflateView=android.widget.LinearLayout{3547387 V.E...... ......ID 0,0-0,0 #7f07004f app:id/ll_inflate_root}inflateView LayoutParams -null
根部局爲空時,也未將生成的view插入到view樹中(也不知道要往哪插)。並且與之前不同的是,LayoutParams爲空!
3. 源碼解析
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// .....
if (TAG_MERGE.equals(name)) {
// merge標籤的解析
// 必須要attach到根節點
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 遞歸的將當前merge標籤下的孩子連接到根節點上
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 其他標籤的解析, temp是當前解析的layout的根節點
// 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) {
// Create layout params that match root, if supplied
// 從解析出來的attr中獲取寬高,創建layoutparam
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
// 如果不attach到root下才進行設置,不然等到addView的時候再進行設置
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
// 遞歸的inflate當前解析佈局根view下的孩子們
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
// 如果root不爲空且要添加到根佈局下時使用addView
if (root != null && attachToRoot) {
// 將temp插入到根節點的最後一個孩子的位置,並設置他的layoutParam爲params
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
// 如果根爲空或者不連接到根節點時返回的是解析的layout.xml的根節點
// 否則返回的爲root
if (root == null || !attachToRoot) {
result = temp;
}
}
// .....
return result;
}
}
/**
* Adds a child view with the specified layout parameters.
*
* <p><strong>Note:</strong> do not invoke this method from
* {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
* @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
// .....
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
// .....
// 如果給定的params是空,則爲其創建一個,這個具體的實現在各個父親那,看他們想咋擺孩子
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
// 設置佈局時不進行requestLayout,即只設置參數不進行刷新,一般用在正在onLayout時
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
// .....
}
大概就是醬紫,簡單來說就是有無root決定加載出來的孩子是否是健全(知不知道自己多大應該在哪兒)的孩子,是否attachToRoot就是這孩子是要跟着他爹還是要自己單飛。
最後推薦一篇講LayoutParams講的不錯的博客自定義控件知識儲備-LayoutParams的那些事