最近在需求中使用到了 ViewStub 来动态加载布局,因此总结一下 ViewStub 的使用和经常使用的布局优化技巧。android
当界面中有些子布局须要知足某些条件才展现时,一般咱们会采用将这个子布局的 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
下面来总结下 ViewStub 的使用方式:.net
使用 layout 属性引用填充的布局文件,不能在 xml 中经过加子控件的方式添加,它是自闭合的(以/>
结尾)。3d
调用方式和普通的 View 同样,在调用 inflate 方法时,返回被填充的 View,能够对 View 中的控件再进行处理,若是不用处理时,能够直接使用 setVisibility 方法。
inflate 方法只能被调用一次,再次调用时会抛出异常,由于在第一次 inflate 以后,ViewStub 被从视图树中移除,就获取不到他的父布局,而在 inflate 方法中须要获取父布局,因此会出错。所以一般须要加 try-catch 进行处理,在 catch 中使用 setVisibility 方法来显示填充的布局。setVisibility 方法中使用了弱引用,能够避免异常抛出。
能够在 ViewStub 中重写填充布局的 layout_ 开头的属性,好比 width、height、layout_margin 等,非 layout 的属性不能重写,而且重写 margin 等属性,必需要先重写 width 和 height 这两个属性,否则不会生效。
在 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 标签须要注意如下几点:
尽量让布局扁平,减小布局嵌套
优先使用 ConstraintLayout(首选)或 RelativeLayout 来编写布局,能够有效地下降布局的嵌套层次。使用 LinearLayout、FrameLayout 会有比较多的布局嵌套。
避免使用 layout_weight属性
layout_weight 属性常常被使用在须要几个控件几等分父布局时,是比较实用的一个属性。可是对每一个设置了这个属性的布局、控件,系统都会执行两次测量过程,在一些须要重复渲染布局的场合(RecyclerView中)会变得严重。所以要避免使用这个属性,相同效果能够改用 ConstraintLayout 或 RelativeLayout 来实现。