Android ViewBinding

三味码屋 2023年03月25日 531次浏览

什么是 ViewBinding

Android 中的一种视图绑定技术,启用 ViewBinding 之后,在编译阶段会根据 XML 布局文件生成对应的 ViewBinding 类,通过 ViewBinding 类实例可以轻松访问 XML 布局文件中的各个视图,相比 findViewById 的方式更加直接、简洁。

如何使用

在要使用 ViewBinding 的 module 的 build.gradle 文件中启用 ViewBinding:
Android Studio 4.0 以下版本:

android {
    ...
    viewBinding {
        enabled = true
    }
}

Android Studio 4.0 及以上版本:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

配置好之后,执行 gradle sync,重新编译代码,将会在 build 目录下生成每个 XML 布局文件对应的 ViewBinding 类。
例如,现在有两个 XML 布局文件:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/Theme.XyAndroid.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/Theme.XyAndroid.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    <include
        android:id="@+id/content"
        layout="@layout/content_main" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/btn_event_stream"
            style="@style/Theme.XyAndroid.ButtonItem"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/sdk_event_stream" />
    </LinearLayout>
</ScrollView>

activity_main.xml 内部通过 include 标签嵌套了 content_main.xml。编译之后,在 build 目录中生成对应的 ViewBinding 类:

image.png

在 MainActivity 中通过调用 ActivityMainBinding 类的 inflate 静态方法加载 activity_main.xml,并访问其中的视图,如下:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)

        binding.content.btnEventStream.setOnClickListener { view ->
            Snackbar.make(view, "点击了 btn_event_stream 按钮", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
    }
}

注意,同样是 activity_main.xml 内部通过 include 标签嵌套 content_main.xml,如果 include 标签没有 id,同时 content_main.xml 的根节点是 merge 标签,则在 MainActivity 中需要通过调用 ContentMainBinding 类的 bind 方法手动将 ContentMainBinding 与 ActivityMainBinding 的根视图 rootView 关联起来:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setSupportActionBar(binding.toolbar)
        val contentMainBinding = ContentMainBinding.bind(binding.root)
        contentMainBinding.btnEventStream.setOnClickListener { view ->
            Snackbar.make(view, "点击了 btn_event_stream 按钮", Snackbar.LENGTH_LONG)
                .setAction("Action", null).show()
        }
    }
}

编译阶段根据 XML 布局文件生成 ViewBinding 类文件的时候,ViewBinding 类文件和内部变量的命名遵循驼峰命名规则,即自动将 XML 文件及其内部视图的下划线命名方式映射为驼峰命名方式。
在 XML 布局文件的根节点中添加 tools:viewBindingIgnore="true" 属性,编译阶段将忽略该 XML 文件,不会生成对应的 ViewBinding 类文件。

原理

  1. 通过 gradle 编译插件,在编译阶段扫描 module 下的 XML 布局文件并生成对应的 ViewBinding 类文件;
  2. ViewBinding 类其实是对 LayoutInflater inflate 布局加载流程、 findViewById 流程、布局中视图控件的封装,ViewBinding 类实例构造完成的时候便已经完成了布局加载和findViewById流程,并通过成员变量的形式持有对视图控件对象的引用;
  3. 通过 ViewBinding 类实例访问各视图控件成员变量。

优缺点

优点

  1. 使用简单,方便
    启用 ViewBinding 的 gradle 配置简单,ViewBinding 类提供的 API 也很简单易用。
  2. 减少自己书写的代码量,代码更简洁
    ViewBinding 类封装了 LayoutInflater inflate 布局加载流程、 findViewById 流程、布局中视图控件,相关代码都不需要自己书写。
  3. 支持 Java 和 Kotlin
    不像 Kotlin Android Extensions 只能在 Kotling 中使用。
  4. 空安全和类型安全
    ViewBinding 类是根据 XML 布局文件生成的,其中的视图控件成员变量和 XML 文件中的视图控件是一一对应的,不会出现找不到视图控件和视图控件类型不匹配的情况。

缺点

  1. 需要在编译阶段生成额外的 ViewBinding 类,会增加编译耗时,且会增大包体积。

参考文章

  1. ViewBinding使用及原理
  2. 安卓ViewBinding详解