ViewStub 使用与布局优化技巧

前言

最近在需求中使用到了 ViewStub 来动态加载布局,因此总结一下 ViewStub 的使用和经常使用的布局优化技巧。android

ViewStub

当界面中有些子布局须要知足某些条件才展现时,一般咱们会采用将这个子布局的 visibility 设为 gone 或 invisible,隐藏这个布局;当条件知足时,设为 visible,让其显示。这样操做比较简单,可是性能表现通常。由于即便 View 隐藏了,仍是在布局中,仍会被父容器绘制,仍会建立对象和实例化、设置属性。bash

Android 中提供了一个轻量级的控件——ViewStub,它是一个不可见的,大小为 0 的视图,能够在运行时动态加载布局资源。当 ViewStub 被设为 Visible 或者调用 inflate方法时,填充资源,而后 ViewStub 自己就被该布局资源替换掉,ViewStub 被从 ViewTree 中移除。这个资源布局在加载时会使用 ViewStub 中定义的 layout 属性去覆盖原来的对应属性,好比 width、height、margin 等。能够经过 inflatedId 属性来定义或重写被填充布局的 id。ide

下面简单写了一个示例,来演示 ViewStub 的使用,代码以下:布局

Activity 的代码:性能

class StubAcitivity : AppCompatActivity() {

    lateinit var viewStub: ViewStub
    var inflatedView: View? = null
    var stubImageView: ImageView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_stub)
        viewStub = findViewById(R.id.view_stub)
    }

    fun showViewStub(view: View) {
        try {
            inflatedView = viewStub.inflate()
        } catch (e: Exception) {
            viewStub.visibility = View.VISIBLE
        }

        stubImageView = inflatedView?.findViewById(R.id.stub_img) as ImageView
        stubImageView?.setImageResource(R.mipmap.ic_launcher)
    }

    fun changeViewStub(view: View) {
        stubImageView?.setImageResource(R.drawable.android10)
    }

    fun hideViewStub(view: View) {
        viewStub.visibility = View.INVISIBLE
    }
}
复制代码

Activity 对应的布局文件:优化

<?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="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="显示ViewStub"
            android:onClick="showViewStub"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更改ViewStub"
            android:onClick="changeViewStub"
            android:layout_marginLeft="20dp"
            android:layout_marginRight="20dp"/>

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="隐藏ViewStub"
            android:onClick="hideViewStub"/>
    </LinearLayout>

    <ViewStub
        android:id="@+id/view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_stub"/>
</LinearLayout>
复制代码

ViewStub 引用的 layout 布局文件:ui

<?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="match_parent">

    <ImageView
        android:id="@+id/stub_img"
        android:layout_width="300dp"
        android:layout_height="300dp"/>
</LinearLayout>
复制代码

效果以下:spa

img1

下面来总结下 ViewStub 的使用方式:.net

  1. 使用 layout 属性引用填充的布局文件,不能在 xml 中经过加子控件的方式添加,它是自闭合的(以/>结尾)。3d

  2. 调用方式和普通的 View 同样,在调用 inflate 方法时,返回被填充的 View,能够对 View 中的控件再进行处理,若是不用处理时,能够直接使用 setVisibility 方法。

  3. inflate 方法只能被调用一次,再次调用时会抛出异常,由于在第一次 inflate 以后,ViewStub 被从视图树中移除,就获取不到他的父布局,而在 inflate 方法中须要获取父布局,因此会出错。所以一般须要加 try-catch 进行处理,在 catch 中使用 setVisibility 方法来显示填充的布局。setVisibility 方法中使用了弱引用,能够避免异常抛出。

  4. 能够在 ViewStub 中重写填充布局的 layout_ 开头的属性,好比 width、height、layout_margin 等,非 layout 的属性不能重写,而且重写 margin 等属性,必需要先重写 width 和 height 这两个属性,否则不会生效。

  5. 在 ViewStub 所加载的布局中不可使用 merge 标签的,所以有可能致使加载出来的布局存在着多余的嵌套结构。

其余的布局优化技巧

使用 include 标签进行布局文件重用

include 标签是很经常使用的一个标签,它使用 layout 属性引用另外一个布局文件,将其添加到 include 标签所在的父容器中。开发中能够把一些公共的布局提取成一个单独的文件,而后使用 include 标签引用,来减小重复编写布局的工做。一样能够在 include 标签中对引用布局的根节点属性进行重写。

<include
	android:layout_width = "match_parent"
	android:layout_height = "wrap_content"
	android:layout="@layout/title_bar"/>
复制代码

使用 merge 标签减小布局嵌套

咱们知道 Android 中 View 的布局嵌套的越多,绘制时间就会越长,性能也越差,因此在编写布局时要尽可能减小布局的嵌套层次

include 标签也有一个缺点,就是可能产生多余的嵌套层次。好比:

activity.xml

<LinearLayout>
	<include layout="@layout/my_layout"/>
</LinearLayout>
复制代码

my_layout.xml

<LinearLayout>
	<TextView/>
</LinearLayout>
复制代码

那么最终结果是:

<LinearLayout>
	<LinearLayout>
		<TextView/>
	</LinearLayout>
</LinearLayout>
复制代码

其中第二个 LinearLayout 就是产生的多余嵌套了。

这时候可使用 merge 标签来配合 include 减小嵌套层次。

my_layout.xml

<merge>
	<TextView/>
</merge>
复制代码

那么结果是:

<LinearLayout>
	<TextView/>
</LinearLayout>
复制代码

其实 merge 就是将其中的子控件直接填充到 include 所在的位置。这样就不会存在多余的嵌套层次了。

使用 merge 标签须要注意如下几点:

  1. merge 标签必须是布局文件的根节点
  2. merge 不是View,经过LayoutInflater.inflate来加载布局时,第二个参数必须选择一个父容器,第三个参数必须为 true,必需要给 merge 标签下的子View指定一个父节点。
  3. 对 merge 标签设置的属性无效

尽量让布局扁平,减小布局嵌套

优先使用 ConstraintLayout(首选)或 RelativeLayout 来编写布局,能够有效地下降布局的嵌套层次。使用 LinearLayout、FrameLayout 会有比较多的布局嵌套。

避免使用 layout_weight属性

layout_weight 属性常常被使用在须要几个控件几等分父布局时,是比较实用的一个属性。可是对每一个设置了这个属性的布局、控件,系统都会执行两次测量过程,在一些须要重复渲染布局的场合(RecyclerView中)会变得严重。所以要避免使用这个属性,相同效果能够改用 ConstraintLayout 或 RelativeLayout 来实现。

参考资料

ViewStub--使用介绍

Android最佳性能实践(四)——布局优化技巧

Android 布局优化 Merge的使用

Android最佳实践之UI篇