在开发中UI布局是我们都会遇到的问题,随着UI越来越多,布局的重复性、复杂度也会随之增长。Android官方给了几个优化的方法,但是网络上的资料基本上都是对官方资料的翻译,这些资料都特别的简单,经常会出现问题而不知其所以然。这篇文章就是对这些问题的更详细的说明,如果有什么不对的也希望高人指出。
include
首先用得最多的应该是include,按照官方的意思,include就是为了解决重复定义相同布局的问题。例如你有五个界面,这五个界面的顶部都有布局一模一样的一个返回按钮和一个文本控件,在不使用include的情况下你在每个界面都需要重新在xml里面写同样的返回按钮和文本控件的顶部栏,这样的重复工作会相当的恶心。使用include标签,我们只需要把这个会被多次使用的顶部栏独立成一个xml文件,然后在需要使用的地方通过include标签引入即可。其实就相当于C语言、C++中的include头文件一样,我们把一些常用的、底层的API封装起来,然后复用,需要的时候引入它即可,而不必每次都自己写一遍。示例如下 :
my_title_layout.xml
01.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
02.
<RelativeLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
03.
android:layout_width=
"match_parent"
04.
android:id=
"@+id/my_title_parent_id"
05.
android:layout_height=
"wrap_content"
>
06.
07.
<ImageButton
08.
android:id=
"@+id/back_btn"
09.
android:layout_width=
"wrap_content"
10.
android:layout_height=
"wrap_content"
11.
android:src=
"@drawable/ic_launcher"
/>
12.
13.
<TextView
14.
android:id=
"@+id/title_tv"
15.
android:layout_width=
"wrap_content"
16.
android:layout_height=
"wrap_content"
17.
android:layout_centerVertical=
"true"
18.
android:layout_marginLeft=
"20dp"
19.
android:layout_toRightOf=
"@+id/back_btn"
20.
android:gravity=
"center"
21.
android:text=
"我的title"
22.
android:textSize=
"18sp"
/>
23.
24.
</RelativeLayout>
01.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
02.
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
03.
android:layout_width=
"match_parent"
04.
android:layout_height=
"match_parent"
05.
android:orientation=
"vertical"
>
06.
07.
<include
08.
android:id=
"@+id/my_title_ly"
09.
android:layout_width=
"match_parent"
10.
android:layout_height=
"wrap_content"
11.
layout=
"@layout/my_title_layout"
/>
12.
13.
</LinearLayout>
注意事项
1、使用include最常见的问题就是findViewById查找不到目标控件,其正确的使用形式如下:
1.
View titleView = findViewById(R.id.my_title_ly) ;
2.
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
3.
titleTextView.setText(
"new Title"
);
1.
View titleView = findViewById(R.id.my_title_parent_id) ;
2.
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;
3.
titleTextView.setText(
"new Title"
);
这样会报空指针异常,因为titleView没有找到,会报空指针。
那么这是怎么回事呢? 我们来分析它的源码看看吧。对于布局文件的解析,最终都会调用到LayoutInflater的inflate方法,该方法最终又会调用rInflate方法,我们看看这个方法。
01.
/**
02.
* Recursive method used to descend down the xml hierarchy and instantiate
03.
* views, instantiate their children, and then call onFinishInflate().
04.
*/
05.
void
rInflate(XmlPullParser parser, View parent,
final
AttributeSet attrs,
06.
boolean
finishInflate)
throws
XmlPullParserException, IOException {
07.
08.
final
int
depth = parser.getDepth();
09.
int
type;
10.
// 迭代xml中的所有元素,挨个解析
11.
while
(((type = parser.next()) != XmlPullParser.END_TAG ||
12.
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
13.
14.
if
(type != XmlPullParser.START_TAG) {
15.
continue
;
16.
}
17.
18.
final
String name = parser.getName();
19.
20.
if
(TAG_REQUEST_FOCUS.equals(name)) {
21.
parseRequestFocus(parser, parent);
22.
}
else
if
(TAG_INCLUDE.equals(name)) {
// 如果xml中的节点是include节点,则调用parseInclude方法
23.
if
(parser.getDepth() ==
0
) {
24.
throw
new
InflateException(
"<include /> cannot be the root element"
);
25.
}
26.
parseInclude(parser, parent, attrs);
27.
}
else
if
(TAG_MERGE.equals(name)) {
28.
throw
new
InflateException(
"<merge /> must be the root element"
);
29.
}
else
if
(TAG_1995.equals(name)) {
30.
final
View view =
new
BlinkLayout(mContext, attrs);
31.
final
ViewGroup viewGroup = (ViewGroup) parent;
32.
final
ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
33.
rInflate(parser, view, attrs,
true
);
34.
viewGroup.addView(view, params);
35.
}
else
{
36.
final
View view = createViewFromTag(parent, name, attrs);
37.
final
ViewGroup viewGroup = (ViewGroup) parent;
38.
final
ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
39.
rInflate(parser, view, attrs,
true
);
40.
viewGroup.addView(view, params);
41.
}
42.
}
43.
44.
if
(finishInflate) parent.onFinishInflate();
45.
}
01.
private
void
parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
02.
throws
XmlPullParserException, IOException {
03.
04.
int
type;
05.
06.
if
(parent
instanceof
ViewGroup) {
07.
final
int
layout = attrs.getAttributeResourceValue(
null
,
"layout"
,
0
);
08.
if
(layout ==
0
) {
// include标签中没有设置layout属性,会抛出异常
09.
final
String value = attrs.getAttributeValue(
null
,
"layout"
);
10.
if
(value ==
null
) {
11.
throw
new
InflateException(
"You must specifiy a layout in the"
12.
+
" include tag: <include layout="
@layout
/layoutID
" />"
);
13.
}
else
{
14.
throw
new
InflateException(
"You must specifiy a valid layout "
15.
+
"reference. The layout ID "
+ value +
" is not valid."
);
16.
}
17.
}
else
{
18.
final
XmlResourceParser childParser =
19.
getContext().getResources().getLayout(layout);
20.
21.
try
{
// 获取属性集,即在include标签中设置的属性
22.
final
AttributeSet childAttrs = Xml.asAttributeSet(childParser);
23.
24.
while
((type = childParser.next()) != XmlPullParser.START_TAG &&
25.
type != XmlPullParser.END_DOCUMENT) {
26.
// Empty.
27.
}
28.
29.
if
(type != XmlPullParser.START_TAG) {
30.
throw
new
InflateException(childParser.getPositionDescription() +
31.
": No start tag found!"
);
32.
}
33.
// 1、解析include中的第一个元素
34.
final
String childName = childParser.getName();
35.
// 如果第一个元素是merge标签,那么调用rInflate函数解析
36.
if
(TAG_MERGE.equals(childName)) {
37.
// Inflate all children.
38.
rInflate(childParser, parent, childAttrs,
false
);
39.
}
else
{
// 2、我们例子中的情况会走到这一步,首先根据include的属性集创建被include进来的xml布局的根view
40.
// 这里的根view对应为my_title_layout.xml中的RelativeLayout
41.
final
View view = createViewFromTag(parent, childName, childAttrs);
42.
final
ViewGroup group = (ViewGroup) parent;
// include标签的parent view
43.
44.
ViewGroup.LayoutParams params =
null
;
45.
try
{
// 获3、取布局属性
46.
params = group.generateLayoutParams(attrs);
47.
}
catch
(RuntimeException e) {
48.
params = group.generateLayoutParams(childAttrs);
49.
}
finally
{
50.
if
(params !=
null
) {
// 被inlcude进来的根view设置布局参数
51.
view.setLayoutParams(params);
52.
}
53.
}
54.
55.
// 4、Inflate all children. 解析所有子控件
56.
rInflate(childParser, view, childAttrs,
true
);
57.
58.
// Attempt to override the included layout's android:id with the
59.
// one set on the <include /> tag itself.
60.
TypedArray a = mContext.obtainStyledAttributes(attrs,
61.
com.android.internal.R.styleable.View,
0
,
0
);
62.
int
id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
63.
// While we're at it, let's try to override android:visibility.
64.
int
visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -
1
);
65.
a.recycle();
66.
// 5、将include中设置的id设置给根view,因此实际上my_title_layout.xml中的RelativeLayout的id会变成include标签中的id,include不设置id,那么也可以通过relative的找到.
67.
if
(id != View.NO_ID) {
68.
view.setId(id);
69.
}
70.
71.
switch
(visibility) {
72.
case
0
:
73.
view.setVisibility(View.VISIBLE);
74.
break
;
75.
case
1
:
76.
view.setVisibility(View.INVISIBLE);
77.
break
;
78.
case
2
:
79.
view.setVisibility(View.GONE);
80.
break
;
81.
}
82.
// 6、将根view添加到父控件中
83.
group.addView(view);
84.
}
85.
}
finally
{
86.
childParser.close();
87.
}
88.
}
89.
}
else
{
90.
throw
new
InflateException(
"<include /> can only be used inside of a ViewGroup"
);
91.
}
92.
93.
final
int
currentDepth = parser.getDepth();
94.
while
(((type = parser.next()) != XmlPullParser.END_TAG ||
95.
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
96.
// Empty
97.
}
98.
}
所以结论就是: 如果include中设置了id,那么就通过include的id来查找被include布局根元素的View;如果include中没有设置Id, 而被include的布局的根元素设置了id,那么通过该根元素的id来查找该view即可。拿到根元素后查找其子控件都是一样的。
ViewStub
我们先看看官方的说明: ViewStub is a lightweight view with no dimension and doesn’t draw anything or participate in the layout. As such, it's cheap to inflate and cheap to leave in a view hierarchy. Each ViewStub simply needs to include the android:layout attribute to specify the layout to inflate.其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。例如我们通过一个ViewStub来惰性加载一个消息流的评论列表,因为一个帖子可能并没有评论,此时我可以不加载这个评论的ListView,只有当有评论时我才把它加载出来,这样就去除了加载ListView带来的资源消耗以及延时,示例如下 :
1.
<ViewStub
2.
android:id=
"@+id/stub_import"
3.
android:inflatedId=
"@+id/stub_comm_lv"
4.
android:layout=
"@layout/my_comment_layout"
5.
android:layout_width=
"fill_parent"
6.
android:layout_height=
"wrap_content"
7.
android:layout_gravity=
"bottom"
/
1.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
2.
<ListView xmlns:android=
"http://schemas.android.com/apk/res/android"
3.
android:layout_width=
"match_parent"
4.
android:id=
"@+id/my_comm_lv"
5.
android:layout_height=
"match_parent"
>
6.
7.
</ListView>
01.
public
class
MainActivity
extends
Activity {
02.
03.
public
void
onCreate(Bundle b){
04.
// main.xml中包含上面的ViewStub
05.
setContentView(R.layout.main);
06.
07.
// 方式1,获取ViewStub,
08.
ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);
09.
// 加载评论列表布局
10.
listStub.setVisibility(View.VISIBLE);
11.
// 获取到评论ListView,注意这里是通过ViewStub的inflatedId来获取
12.
ListView commLv = findViewById(R.id.stub_comm_lv);
13.
if
( listStub.getVisibility() == View.VISIBLE ) {
14.
// 已经加载, 否则还没有加载
15.
}
16.
}
17.
}
这是为什么呢 ?
我们先看ViewStub的部分代码吧:
01.
@SuppressWarnings
({
"UnusedDeclaration"
})
02.
public
ViewStub(Context context, AttributeSet attrs,
int
defStyle) {
03.
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,
04.
defStyle,
0
);
05.
// 获取inflatedId属性
06.
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
07.
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout,
0
);
08.
09.
a.recycle();
10.
11.
a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle,
0
);
12.
mID = a.getResourceId(R.styleable.View_id, NO_ID);
13.
a.recycle();
14.
15.
initialize(context);
16.
}
17.
18.
private
void
initialize(Context context) {
19.
mContext = context;
20.
setVisibility(GONE);
// 设置不可教案
21.
setWillNotDraw(
true
);
// 设置不绘制
22.
}
23.
24.
@Override
25.
protected
void
onMeasure(
int
widthMeasureSpec,
int
heightMeasureSpec) {
26.
setMeasuredDimension(
0
,
0
);
// 宽高都为0
27.
}
28.
29.
30.
@Override
31.
public
void
setVisibility(
int
visibility) {
32.
if
(mInflatedViewRef !=
null
) {
// 如果已经加载过则只设置Visibility属性
33.
View view = mInflatedViewRef.get();
34.
if
(view !=
null
) {
35.
view.setVisibility(visibility);
36.
}
else
{
37.
throw
new
IllegalStateException(
"setVisibility called on un-referenced view"
);
38.
}
39.
}
else
{
// 如果未加载,这加载目标布局
40.
super
.setVisibility(visibility);
41.
if
(visibility == VISIBLE || visibility == INVISIBLE) {
42.
inflate();
// 调用inflate来加载目标布局
43.
}
44.
}
45.
}
46.
47.
/**
48.
* Inflates the layout resource identified by {@link #getLayoutResource()}
49.
* and replaces this StubbedView in its parent by the inflated layout resource.
50.
*
51.
* @return The inflated layout resource.
52.
*
53.
*/
54.
public
View inflate() {
55.
final
ViewParent viewParent = getParent();
56.
57.
if
(viewParent !=
null
&& viewParent
instanceof
ViewGroup) {
58.
if
(mLayoutResource !=
0
) {
59.
final
ViewGroup parent = (ViewGroup) viewParent;
// 获取ViewStub的parent view,也是目标布局根元素的parent view
60.
final
LayoutInflater factory = LayoutInflater.from(mContext);
61.
final
View view = factory.inflate(mLayoutResource, parent,
62.
false
);
// 1、加载目标布局
63.
// 2、如果ViewStub的inflatedId不是NO_ID则把inflatedId设置为目标布局根元素的id,即评论ListView的id
64.
if
(mInflatedId != NO_ID) {
65.
view.setId(mInflatedId);
66.
}
67.
68.
final
int
index = parent.indexOfChild(
this
);
69.
parent.removeViewInLayout(
this
);
// 3、将ViewStub自身从parent中移除
70.
71.
final
ViewGroup.LayoutParams layoutParams = getLayoutParams();
72.
if
(layoutParams !=
null
) {
73.
parent.addView(view, index, layoutParams);
// 4、将目标布局的根元素添加到parent中,有参数
74.
}
else
{
75.
parent.addView(view, index);
// 4、将目标布局的根元素添加到parent中
76.
}
77.
78.
mInflatedViewRef =
new
WeakReference<View>(view);
79.
80.
if
(mInflateListener !=
null
) {
81.
mInflateListener.onInflate(
this
, view);
82.
}
83.
84.
return
view;
85.
}
else
{
86.
throw
new
IllegalArgumentException(
"ViewStub must have a valid layoutResource"
);
87.
}
88.
}
else
{
89.
throw
new
IllegalStateException(
"ViewStub must have a non-null ViewGroup viewParent"
);
90.
}
91.
}
可以看到,其实最终加载目标布局的还是inflate()函数,在该函数中将加载目标布局,获取到根元素后,如果mInflatedId不为NO_ID则把mInflatedId设置为根元素的id,这也是为什么我们在获取评论ListView时会使用findViewById(R.id.stub_comm_lv)来获取,其中的stub_comm_lv就是ViewStub的inflatedId。当然如果你没有设置inflatedId的话还是可以通过评论列表的id来获取的,例如findViewById(R.id.my_comm_lv)。然后就是ViewStub从parent中移除、把目标布局的根元素添加到parent中。最后会把目标布局的根元素返回,因此我们在调用inflate()函数时可以直接获得根元素,省掉了findViewById的过程。
还有一种方式加载目标布局的就是直接调用ViewStub的inflate()方法,示例如下 :
01.
public
class
MainActivity
extends
Activity {
02.
03.
// 把commLv2设置为类的成员变量
04.
ListView commLv2 =
null
;
05.
//
06.
public
void
onCreate(Bundle b){
07.
// main.xml中包含上面的ViewStub
08.
setContentView(R.layout.main);
09.
10.
// 方式二
11.
ViewStub listStub2 = (ViewStub) findViewById(R.id.stub_import) ;
12.
// 成员变量commLv2为空则代表未加载
13.
if
( commLv2 ==
null
) {
14.
// 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象
15.
commLv2 = (ListView)listStub2.inflate();
16.
}
else
{
17.
// ViewStub已经加载
18.
}
19.
20.
}
21.
22.
}
注意事项
1、判断是否已经加载过, 如果通过setVisibility来加载,那么通过判断可见性即可;如果通过inflate()来加载是不可以通过判断可见性来处理的,而需要使用方式2来进行判断。
2、findViewById的问题,注意ViewStub中是否设置了inflatedId,如果设置了则需要通过inflatedId来查找目标布局的根元素。
Merge
首先我们看官方的说明:
The <merge /> tag helps eliminate redundant view groups in your view hierarchy when including one layout within another. For example, if your main layout is a vertical LinearLayout in which two consecutive views can be re-used in multiple layouts, then the re-usable layout in which you place the two views requires its own root view. However, using another LinearLayout as the root for the re-usable layout would result in a vertical LinearLayout inside a vertical LinearLayout. The nested LinearLayout serves no real purpose other than to slow down your UI performance.
其实就是减少在include布局文件时的层级。<merge>标签是这几个标签中最让我费解的,大家可能想不到,<merge>标签竟然会是一个Activity,里面有一个LinearLayout对象。
01.
/**
02.
* Exercise <merge /> tag in XML files.
03.
*/
04.
public
class
Merge
extends
Activity {
05.
private
LinearLayout mLayout;
06.
07.
@Override
08.
protected
void
onCreate(Bundle icicle) {
09.
super
.onCreate(icicle);
10.
11.
mLayout =
new
LinearLayout(
this
);
12.
mLayout.setOrientation(LinearLayout.VERTICAL);
13.
LayoutInflater.from(
this
).inflate(R.layout.merge_tag, mLayout);
14.
15.
setContentView(mLayout);
16.
}
17.
18.
public
ViewGroup getLayout() {
19.
return
mLayout;
20.
}
21.
}
使用merge来组织子元素可以减少布局的层级。例如我们在复用一个含有多个子控件的布局时,肯定需要一个ViewGroup来管理,例如这样 :
01.
<FrameLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
02.
android:layout_width=
"fill_parent"
03.
android:layout_height=
"fill_parent"
>
04.
05.
<ImageView
06.
android:layout_width=
"fill_parent"
07.
android:layout_height=
"fill_parent"
08.
09.
android:scaleType=
"center"
10.
android:src=
"@drawable/golden_gate"
/>
11.
12.
<TextView
13.
android:layout_width=
"wrap_content"
14.
android:layout_height=
"wrap_content"
15.
android:layout_marginBottom=
"20dip"
16.
android:layout_gravity=
"center_horizontal|bottom"
17.
18.
android:padding=
"12dip"
19.
20.
android:background=
"#AA000000"
21.
android:textColor=
"#ffffffff"
22.
23.
android:text=
"Golden Gate"
/>
24.
25.
</FrameLayout>
使用merge标签就会消除上图中蓝色的FrameLayout层级。示例如下 :
01.
<merge xmlns:android=
"http://schemas.android.com/apk/res/android"
>
02.
03.
<ImageView
04.
android:layout_width=
"fill_parent"
05.
android:layout_height=
"fill_parent"
06.
07.
android:scaleType=
"center"
08.
android:src=
"@drawable/golden_gate"
/>
09.
10.
<TextView
11.
android:layout_width=
"wrap_content"
12.
android:layout_height=
"wrap_content"
13.
android:layout_marginBottom=
"20dip"
14.
android:layout_gravity=
"center_horizontal|bottom"
15.
16.
android:padding=
"12dip"
17.
18.
android:background=
"#AA000000"
19.
android:textColor=
"#ffffffff"
20.
21.
android:text=
"Golden Gate"
/>
22.
23.
</merge>
那么它是如何实现的呢,我们还是看源码吧。相关的源码也是在LayoutInflater的inflate()函数中。
01.
public
View inflate(XmlPullParser parser, ViewGroup root,
boolean
attachToRoot) {
02.
synchronized
(mConstructorArgs) {
03.
final
AttributeSet attrs = Xml.asAttributeSet(parser);
04.
Context lastContext = (Context)mConstructorArgs[
0
];
05.
mConstructorArgs[
0
] = mContext;
06.
View result = root;
07.
08.
try
{
09.
// Look for the root node.
10.
int
type;
11.
while
((type = parser.next()) != XmlPullParser.START_TAG &&
12.
type != XmlPullParser.END_DOCUMENT) {
13.
// Empty
14.
}
15.
16.
if
(type != XmlPullParser.START_TAG) {
17.
throw
new
InflateException(parser.getPositionDescription()
18.
+
": No start tag found!"
);
19.
}
20.
21.
final
String name = parser.getName();
22.
23.
// m如果是erge标签,那么调用rInflate进行解析
24.
if
(TAG_MERGE.equals(name)) {
25.
if
(root ==
null
|| !attachToRoot) {
26.
throw
new
InflateException(
"<merge /> can be used only with a valid "
27.
+
"ViewGroup root and attachToRoot=true"
);
28.
}
29.
// 解析merge标签
30.
rInflate(parser, root, attrs,
false
);
31.
}
else
{
32.
// 代码省略
33.
}
34.
35.
}
catch
(XmlPullParserException e) {
36.
// 代码省略
37.
}
38.
39.
return
result;
40.
}
41.
}
42.
43.
44.
void
rInflate(XmlPullParser parser, View parent,
final
AttributeSet attrs,
45.
boolean
finishInflate)
throws
XmlPullParserException, IOException {
46.
47.
final
int
depth = parser.getDepth();
48.
int
type;
49.
50.
while
(((type = parser.next()) != XmlPullParser.END_TAG ||
51.
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
52.
53.
if
(type != XmlPullParser.START_TAG) {
54.
continue
;
55.
}
56.
57.
final
String name = parser.getName();
58.
59.
if
(TAG_REQUEST_FOCUS.equals(name)) {
60.
parseRequestFocus(parser, parent);
61.
}
else
if
(TAG_INCLUDE.equals(name)) {
62.
if
(parser.getDepth() ==
0
) {
63.
throw
new
InflateException(
"<include /> cannot be the root element"
);
64.
}
65.
parseInclude(parser, parent, attrs);
66.
}
else
if
(TAG_MERGE.equals(name)) {
67.
throw
new
InflateException(
"<merge /> must be the root element"
);
68.
}
else
if
(TAG_1995.equals(name)) {
69.
final
View view =
new
BlinkLayout(mContext, attrs);
70.
final
ViewGroup viewGroup = (ViewGroup) parent;
71.
final
ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
72.
rInflate(parser, view, attrs,
true
);
73.
viewGroup.addView(view, params);
74.
}
else
{
// 我们的例子会进入这里
75.
final
View view = createViewFromTag(parent, name, attrs);
76.
// 获取merge标签的parent
77.
final
ViewGroup viewGroup = (ViewGroup) parent;
78.
// 获取布局参数
79.
final
ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
80.
// 递归解析每个子元素
81.
rInflate(parser, view, attrs,
true
);
82.
// 将子元素直接添加到merge标签的parent view中
83.
viewGroup.addView(view, params);
84.
}
85.
}
86.
87.
if
(finishInflate) parent.onFinishInflate();
88.
}