您的位置:首页 > 移动开发 > Android开发

Android布局优化之Merge Include ViewStub使用与源码分析

2016-01-29 12:32 731 查看

在开发中UI布局是我们都会遇到的问题,随着UI越来越多,布局的重复性、复杂度也会随之增长。Android官方给了几个优化的方法,但是网络上的资料基本上都是对官方资料的翻译,这些资料都特别的简单,经常会出现问题而不知其所以然。这篇文章就是对这些问题的更详细的说明,如果有什么不对的也希望高人指出。

include

首先用得最多的应该是include,按照官方的意思,include就是为了解决重复定义相同布局的问题。例如你有五个界面,这五个界面的顶部都有布局一模一样的一个返回按钮和一个文本控件,在不使用include的情况下你在每个界面都需要重新在xml里面写同样的返回按钮和文本控件的顶部栏,这样的重复工作会相当的恶心。使用include标签,我们只需要把这个会被多次使用的顶部栏独立成一个xml文件,然后在需要使用的地方通过include标签引入即可。其实就相当于C语言、C++中的include头文件一样,我们把一些常用的、底层的API封装起来,然后复用,需要的时候引入它即可,而不必每次都自己写一遍。示例如下
:

my_title_layout.xml

 

view sourceprint?

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>


include布局文件:
 

 

view sourceprint?

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>


这样我们就可以使用my_title_layout了。
 

注意事项

1、使用include最常见的问题就是findViewById查找不到目标控件,其正确的使用形式如下:

 

view sourceprint?

1.
View titleView = findViewById(R.id.my_title_ly) ;

2.
     
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;

3.
     
titleTextView.setText(
"new Title"
);


首先找到include的id,例如这里include设置的id为“my_title_ly”,然后再对获取到的titleView.findViewById来查找目标布局中的子控件,例如title_tv就是my_title_layout.xml中定义的子控件。因此我们如果需要查找控件的话,可以设置include标签的id,通过这个id获取include对应的view以后,再通过对这个view进行findViewById才能正确查找。如果你设置了include标签的id,然后通过被include的布局的root
view的id来查找子元素的话,则会报错,如下 :

view sourceprint?

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方法,我们看看这个方法。

 

view sourceprint?

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.
}


这个方法其实就是遍历xml中的所有元素,然后挨个进行解析。例如解析到一个<TextView>标签,那么就根据用户设置的一些layout_width、layout_height、id等属性来构造一个TextView对象,然后添加到父控件(ViewGroup类型)中。<include>标签也是一样的,我们看到遇到include标签时,会调用parseInclude函数,这就是对<include>标签的解析,我们看看吧。
 

 

view sourceprint?

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元素,然后再解析被include进来的布局的root view元素。在我们的例子中对应的root view就是id为my_title_parent_id的RelativeLayout,然后再解析root view下面的所有元素,这个过程是从上面注释的2~4的过程,然后是设置布局参数。我们注意看注释5处,这里就解释了为什么include标签和被引入的布局的根元素都设置了id的情况下,通过被引入的根元素的id来查找子控件会找不到的情况。我们看到,注释5处的会判断include标签的id如果不是View.NO_ID的话会把该id设置给被引入的布局根元素的id,即此时在我们的例子中被引入的id为my_title_parent_id的根元素RelativeLayout的id被设置成了include标签中的id,即RelativeLayout的id被动态修改成了"my_title_ly"。因此此时我们再通过“my_title_parent_id”这个id来查找根元素就会找不到了!
所以结论就是: 如果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带来的资源消耗以及延时,示例如下 :

view sourceprint?

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"

/


my_comment_layout.xml如下:

view sourceprint?

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>


在运行时,我们只需要控制id为stub_import的ViewStub的可见性或者调用inflate()函数来控制是否加载这个评论列表即可。示例如下 :

view sourceprint?

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.
       
}


通过setVisibility(View.VISIBILITY)来加载评论列表,此时你要获取到评论ListView对象的话,则需要通过findViewById来查找,而这个id并不是就是ViewStub的id。

这是为什么呢 ?

我们先看ViewStub的部分代码吧:
 

view sourceprint?

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()方法,示例如下 :

 

view sourceprint?

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对象。
 

 

view sourceprint?

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来管理,例如这样 :

 

view sourceprint?

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>


将该布局通过include引入时就会多引入了一个FrameLayout层级,此时结构如下 :
 



使用merge标签就会消除上图中蓝色的FrameLayout层级。示例如下 :

 

view sourceprint?

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()函数中。

 

view sourceprint?

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.
   
}


其实就是如果是merge标签,那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息