| by Jim | No comments

Android布局优化之ViewStub、include、merge

  在开发中UI布局是我们都会遇到的问题,随着UI越来越多,布局的重复性、复杂度也会随之增长。对此我们优化xml布局就不得不说重用布局,为了有效地重新使用完整的布局,Google提出可以使用和这两个非常有用的标签,用以在当前布局中嵌入另一个布局,下面我们就来逐个学习一下。

一、include

  标签可以允许在一个布局当中引入另外一个布局,那么比如说我们程序的所有界面都有一个公共的部分,这个时候最好的做法就是将这个公共的部分提取到一个独立的布局文件当中,然后在每个界面的布局文件当中来引用这个公共的布局。这里举个例子吧,例如在include_layout.xml添加两个按钮:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button One"
        android:id="@+id/button" />
    <Button
        android:layout_width="match_parent" 
       android:layout_height="wrap_content"
        android:text="Button Two"
        android:id="@+id/button2" />
</LinearLayout>

  好的,那这连个按钮作为一个独立的布局现在我们已经编写完了,接下来的工作就非常简单了,无论任何界面需要加入这个布局,只需要在布局文件中引入include_layout.xml就可以了。那么比如说我们的程序当中有一个activity_main.xml文件,现在想要引入include_layout.xml只需要这样写:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button"
        android:onClick="onClick"
        android:id="@+id/button1"/>
    <include layout="@layout/include_layout"/>
</LinearLayout>

显示的效果为:


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

  可以看到,最外层首先是一个FrameLayout,至于为什么是FrameLayout,不知道的朋友可以去参考 Android LayoutInflater原理分析,带你一步步深入了解View(一) 这篇文章。然后FrameLayout中包含的是一个LinearLayout,这个就是我们在activity_main.xml中定义的最外层布局了。

二、merge

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

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button One"
        android:id="@+id/button" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button Two"
        android:id="@+id/button2" />
</merge>

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

merge.PNG


  好了,可以看到,现在两个按钮都直接包含在了LinearLayout下面,我们的include_layout.xml当中也就不存在多余的布局嵌套了。

三、ViewStub

  对于ViewStub(请自备梯子)可以理解为仅在需要时才加载布局。
  ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。这样,就可以使用ViewStub来方便的在运行时,要不要显示某个布局。
  但ViewStub也不是万能的,下面总结下ViewStub能做的事儿和什么时候该用ViewStub,什么时候该用可见性的控制。
首先来说说ViewStub的一些特点:

  1. ViewStub只能Inflate一次,之后ViewStub对象会被置为空。按句话说,某个被ViewStub指定的布局被Inflate后,就不会够再通过ViewStub来控制它了。
  2. ViewStub只能用来Inflate一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。

基于以上的特点,那么可以考虑使用ViewStub的情况有:

  1. 在程序的运行期间,某个布局在Inflate后,就不会有变化,除非重新启动。因为ViewStub只能Inflate一次,之后会被置空,所以无法指望后面接着使用ViewStub来控制布局。所以当需要在运行时不止一次的显示和隐藏某个布局,那么ViewStub是做不到的。这时就只能使用View的可见性来控制了。
  2. 想要控制显示与隐藏的是一个布局文件,而非某个View。因为设置给ViewStub的只能是某个布局文件的Id,所以无法让它来控制某个View。

  所以,如果想要控制某个View(如Button或TextView)的显示与隐藏,或者想要在运行时不断的显示与隐藏某个布局或View,只能使用View的可见性来控制。接下来修改include_layout.xml布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button"
        android:onClick="onClick"
        android:id="@+id/button1"/>
    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/include_layout"/>
</LinearLayout>

现在看看MainActivity的代码:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    void onClick(View view){
        ViewStub viewStub= (ViewStub) findViewById(view_stub);
       if (viewStub!=null){
           viewStub.inflate();
       }
    }
}

最后来看看使用ViewStub的效果:

GIF.gif


点击button就会调用ViewStub的inflate();方法来加载我们需要的布局
最后使用ViewStub需要注意:

  1. 某些布局属性要加在ViewStub而不是实际的布局上面,才会起作用,比如上面用的android:layout_margin*系列属性,如果加在TextView上面,则不会起作用,需要放在它的ViewStub上面才会起作用。而ViewStub的属性在inflate()后会都传给相应的布局。

  附上UI Automator Viewer工具的打开方法:AndroidSDK > tools > uiautomatorviewer.bat


  最后,如果大家想要继续学习更多关于性能优化的技巧,可以到这个网址上阅读更多内容 http://developer.android.com/training/best-performance.html 。

发表评论