Android布局总结四:Merge总结

引言

merge标签是作为include标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。大家都知道,Android去解析和展示一个布局是需要消耗时间的,布局嵌套的越多,那么解析起来就越耗时,性能也就越差,因此我们在编写布局文件时应该让嵌套的层数越少越好。

include标签的缺点

Android布局总结三:include总结 我们讲解include标签的用法时主要介绍了它优点,但是它也存在着一个不好的地方,就是可能会导致产生多余的布局嵌套。这里还是通过举例的方式跟大家说明一下,比如说我们需要编写一个确定取消按钮的公共布局,这样任何一个界面需要确定和取消功能时就不用再单独编写了,新建ok_cancel_layout.xml,代码如下所示:

<?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="wrap_content"
    android:orientation="vertical" >

    <Button
        android:id="@+id/ok"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:text="OK" />

    <Button
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:text="Cancel" />

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

可以看到,这个界面也是非常简单,外层是一个垂直方向的LinearLayout,LinearLayout中包含了两个按钮,一个用于实现确定功能,一个用于实现取消功能。现在我们可以来预览一下这个界面,如下图所示:

这里写图片描述

好的,然后我们有一个profile.xml的界面需要编辑一些内容,那么这里就可以将ok_cancel_layout这个布局引入到profile.xml界面当中,如下所示:

<?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:orientation="vertical" >

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:hint="Edit something here" />

    <include layout="@layout/ok_cancel_layout"/>

</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在profile.xml当中有一个EditText控件用于编辑内容,然后下面使用了标签来将ok_cancel_layout布局进行引入,现在重新运行一下程序,界面效果如下图所示:

这里写图片描述

看上去效果非常不错对吗?可是在你毫无察觉的情况下,目前profile.xml这个界面当中其实已经存在着多余的布局嵌套了!感觉还没写几行代码呢,怎么这就已经有多余的布局嵌套了?不信的话我们可以通过View Hierarchy工具来查看一下,如下图所示:

这里写图片描述

可以看到,最外层首先是一个FrameLayout,这个无可厚非,不知道为什么最外层是FrameLayout的朋友可以去参考 Android LayoutInflater原理分析,带你一步步深入了解View(一) 这篇文章。然后FrameLayout中包含的是一个LinearLayout,这个就是我们在profile.xml中定义的最外层布局。接下来的部分就有问题了,在最外层的LinearLayout当中包含了两个元素,一个是EditText,另一个又是一个LinearLayout,然后在这个内部的LinearLayout当中才包含了确定和取消这两个按钮。

使用merge标签

相信大家已经可以看出来了吧,这个内部的LinearLayout就是一个多余的布局嵌套,实际上并不需要这样一层,让两个按钮直接包含在外部的LinearLayout当中就可以了。而这个多余的布局嵌套其实就是由于布局引入所导致的,因为我们在ok_cancel_layout.xml中也定义了一个LinearLayout。那么应该怎样优化掉这个问题呢?当然就是使用merge标签来完成了,修改ok_cancel_layout.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <Button
        android:id="@+id/ok"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:text="OK" />

    <Button
        android:id="@+id/cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:text="Cancel" />

</merge>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

可以看到,这里我们将ok_cancel_layout最外层的LinearLayout布局删除掉,换用了merge标签,这就表示当有任何一个地方去include这个布局时,会将merge标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构。好的,merge的用法就是这么简单,现在重新运行一下程序,你会看到界面没有任何改变,然后我们再通过View Hierarchy工具来查看一下当前的View结构,如下图所示:

这里写图片描述

merge标签原理

其实就是减少在include布局文件时的层级。标签是这几个标签中最让我费解的,大家可能想不到,标签竟然会是一个Activity,里面有一个LinearLayout对象。

/** 
 * Exercise <merge /> tag in XML files. 
 */  
public class Merge extends Activity {  
    private LinearLayout mLayout;  

    @Override  
    protected void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  

        mLayout = new LinearLayout(this);  
        mLayout.setOrientation(LinearLayout.VERTICAL);  
        LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);  

        setContentView(mLayout);  
    }  

    public ViewGroup getLayout() {  
        return mLayout;  
    }  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

使用merge来组织子元素可以减少布局的层级。例如我们在复用一个含有多个子控件的布局时,肯定需要一个ViewGroup来管理,例如这样 :

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent">  

    <ImageView    
        android:layout_width="fill_parent"   
        android:layout_height="fill_parent"   

        android:scaleType="center"  
        android:src="@drawable/golden_gate" />  

    <TextView  
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content"   
        android:layout_marginBottom="20dip"  
        android:layout_gravity="center_horizontal|bottom"  

        android:padding="12dip"  

        android:background="#AA000000"  
        android:textColor="#ffffffff"  

        android:text="Golden Gate" />  

</FrameLayout> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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

这里写图片描述

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

<merge xmlns:android="http://schemas.android.com/apk/res/android">  

    <ImageView    
        android:layout_width="fill_parent"   
        android:layout_height="fill_parent"   

        android:scaleType="center"  
        android:src="@drawable/golden_gate" />  

    <TextView  
        android:layout_width="wrap_content"   
        android:layout_height="wrap_content"   
        android:layout_marginBottom="20dip"  
        android:layout_gravity="center_horizontal|bottom"  

        android:padding="12dip"  

        android:background="#AA000000"  
        android:textColor="#ffffffff"  

        android:text="Golden Gate" />  

</merge>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

效果图如下 :

这里写图片描述

那么它是如何实现的呢,我们还是看源码吧。相关的源码也是在LayoutInflater的inflate()函数中。

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
       synchronized (mConstructorArgs) {  
           final AttributeSet attrs = Xml.asAttributeSet(parser);  
           Context lastContext = (Context)mConstructorArgs[0];  
           mConstructorArgs[0] = mContext;  
           View result = root;  

           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();  

               // m如果是erge标签,那么调用rInflate进行解析  
               if (TAG_MERGE.equals(name)) {  
                   if (root == null || !attachToRoot) {  
                       throw new InflateException("<merge /> can be used only with a valid "  
                               + "ViewGroup root and attachToRoot=true");  
                   }  
                   // 解析merge标签  
                   rInflate(parser, root, attrs, false);  
               } else {  
                  // 代码省略  
               }  

           } catch (XmlPullParserException e) {  
               // 代码省略  
           }   

           return result;  
       }  
   }  


      void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs,  
           boolean finishInflate) throws XmlPullParserException, IOException {  

       final int depth = parser.getDepth();  
       int type;  

       while (((type = parser.next()) != XmlPullParser.END_TAG ||  
               parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {  

           if (type != XmlPullParser.START_TAG) {  
               continue;  
           }  

           final String name = parser.getName();  

           if (TAG_REQUEST_FOCUS.equals(name)) {  
               parseRequestFocus(parser, parent);  
           } else if (TAG_INCLUDE.equals(name)) {  
                // 代码省略 
               parseInclude(parser, parent, attrs);  
           } else if (TAG_MERGE.equals(name)) {  
               throw new InflateException("<merge /> must be the root element");  
           } else if (TAG_1995.equals(name)) {  
               final View view = new BlinkLayout(mContext, attrs);  
               final ViewGroup viewGroup = (ViewGroup) parent;  
               final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
               rInflate(parser, view, attrs, true);  
               viewGroup.addView(view, params);                  
           } else { // 我们的例子会进入这里  
               final View view = createViewFromTag(parent, name, attrs);  
               // 获取merge标签的parent  
               final ViewGroup viewGroup = (ViewGroup) parent;  
               // 获取布局参数  
               final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
               // 递归解析每个子元素  
               rInflate(parser, view, attrs, true);  
               // 将子元素直接添加到merge标签的parent view中  
               viewGroup.addView(view, params);  
           }  
       }  

       if (finishInflate) parent.onFinishInflate();  
   }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86

如果使用include标签,那么直接将其中的子元素添加到merge标签parent中,这样就保证了不会引入额外的层级

posted @ 2019-05-25 10:39  天涯海角路  阅读(2842)  评论(2编辑  收藏  举报