使用视图绑定替代 findViewById

从 Android Studio 3.6 开始,视图绑定可以经过生成绑定对象来替代 findViewById,从而能够帮您简化代码、移除 bug,而且从 findViewById 的模版代码中解脱出来。html

本文梗概java

  • 在 build.gradle 中就能够方便快捷地开启视图绑定且无须额外引入依赖库
  • 视图绑定会为 Module 中的每个布局文件生成一个绑定对象

(activity_awesome.xmlActivityAwesomeBinding.java)android

  • 布局文件中每个带有 id 的视图都会在绑定对象中有一个对应的属性,这个属性将拥有正确的类型,而且空安全
  • 视图绑定完美支持 Java 和 Kotlin 编程语言

更多详细内容能够从视频:使用视图绑定替代 findViewById 中获取。git

在 build.gradle 中开启视图绑定

开启视图绑定无须引入额外依赖,从 Android Studio 3.6 开始,视图绑定将会内建于 Android Gradle 插件中。须要打开视图绑定的话,只须要在 build.gradle 文件中配置 viewBinding 选项:github

// 须要 Android Gradle Plugin 3.6.0
android {
    viewBinding {
        enabled = true
    }
}

在 Android Studio 4.0 中,viewBinding 变成属性被整合到了 buildFeatures 选项中,因此配置要改为:编程

// Android Studio 4.0
android {
    buildFeatures {
        viewBinding = true
    }
}

配置完成后,视图绑定就会为全部布局文件自动生成对应的绑定类。无须修改原有布局的 XML 文件,视图绑定将根据您现有的布局自动完成全部工做。安全

视图绑定将会根据现有的 XML 文件,为 Module 内全部的布局文件生成绑定对象。

您能够在任何须要填充布局的地方使用绑定对象,好比 FragmentActivity、甚至是 RecyclerView Adapter(或者说是 ViewHolder 中)。编程语言

在 Activity 中使用视图绑定

假如您有一个布局文件名叫 activity_awesome.xml,其中包含了一个按钮和两个文本视图。视图绑定会为这个布局生成一个名叫 ActivityAwesomeBinding 的类,布局文件中全部拥有 id 的视图,都会在这个类中有一个对应的属性:ide

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ActivityAwesomeBinding.inflate(layoutInflater)

    binding.title.text = "Hello"
    binding.subtext.text = "Concise, safe code"
    binding.button.setOnClickListener { /* ... */ }

    setContentView(binding.root)
}

△ 在 Activity 中使用视图绑定布局

使用视图绑定时,无须再调用 findViewById 方法,只要直接调用绑定对象中的对应属性便可。

布局的根视图(不管有没有 id)都会自动生成一个名为 root 的属性。在 ActivityonCreate 方法中,要将 root 传入 setContentView 方法,从而让 Activity 可使用绑定对象中的布局。

一个常见的错误用法是: 在开启了视图绑定的同时,依然在 setContentView(...) 中传入布局的 id 而不是绑定对象。这将形成同一布局被填充两次,同时监听器也会被添加到错误的布局对象中。

解决方案: 在 Activity 中使用视图绑定时,必定要将绑定对象的 root 属性传入 setContentView() 方法中。

使用绑定对象编写安全性更佳的代码

findViewById 是许多用户可见 bug 的来源: 咱们很容易传入一个布局中根本不存在的 id,从而致使空指针异常而崩溃;因为此方法类型不安全,也很容易令人写出像 findViewById<TextView>(R.id.image) 这样的,致使类型转换错误的代码。为了解决这些问题,视图绑定把 findViewById 替换成了更加简洁和安全的实现。

视图绑定有下面两个特性:

  • 类型安全: 由于视图绑定老是会基于布局中的视图生成类型正确的属性。因此若是您在布局中放入了一个 TextView ,视图绑定就会暴露一个 TextView 类型的属性给您。
  • 空安全: 视图绑定会检测某个视图是否是只在一些配置下存在,并依据结果生成带有 @Nullable 注解的属性。因此即便在多种配置下定义的布局文件,视图绑定依然可以保证空安全。

因为生成的绑定类是普通的 Java 类,而且其中添加了 Kotlin 友好的注解,因此 Java 和 Kotlin 均可以使用视图绑定。

视图绑定生成的代码是怎样的

如前文所说,视图绑定会生成一个包含替代 findViewById 功能的 Java 类。它会为 Module 下的每个布局的 XML 文件生成一个对应的绑定对象,并根据源文件为其命名,好比 activity_awesome.xml 对应的绑定对象为 ActivityAwesomeBinding.java。

生成代码的逻辑被优化为,当您在 Android Studio 中编辑 XML 布局文件时,只会更新所修改布局对应的绑定对象。同时这些工做会在内存中运行,从而使这个过程能够迅速完成。这意味着您的修改会当即反映在绑定对象中,而无须等待或者从新构建工程。

Android Studio 被优化为能够在您编辑过 XML 布局文件后当即更新绑定对象。

让咱们经过一个 示例 XML 布局 所生成的代码,来了解一下视图绑定究竟生成了什么。

public final class ActivityAwesomeBinding implements ViewBinding {
  @NonNull
  private final ConstraintLayout rootView;
  @NonNull
  public final Button button;
  @NonNull
  public final TextView subtext;
  @NonNull
  public final TextView title;

△ 视图绑定生成的属性。能够看到它们都是类型安全以及空安全的

视图绑定会根据每一个拥有 id 的视图生成类型正确的属性。他也会为根布局生成 rootView 属性并经过 getRoot 暴露给您。视图绑定没有添加任何额外的逻辑,他只是把视图属性暴露给您,从而帮您在不使用 findViewById 的状况下也能调用它们。这样一来便保证了生成文件简洁性(固然也避免了拖慢构建速度)。

若是您正在使用 Kotlin,视图绑定的生成类也已经对互操做进行了优化。经过 @Nullable@NonNull 注解的使用,Kolin 能够正确的将属性暴露为空安全类型。若是想要了解更多关于两种语言的互操做问题,请查阅文档: 在 Kotlin 中调用 Java

private ActivityAwesomeBinding(@NonNull ConstraintLayout rootView, @NonNull Button button,
      @NonNull TextView subtext, @NonNull TextView title) { … }

  @NonNull
  public static ActivityAwesomeBinding inflate(@NonNull LayoutInflater inflater) {
    /* 编辑过: 移除了重载方法 inflate(inflater, parent, attachToParent) 的调用*/
    View root = inflater.inflate(R.layout.activity_awesome, null, false);
    return bind(root);
  }

视图绑定会生成 inflate 方法做为生成一个绑定对象实例的主要方式。在 ActivityAwesomeBinding.java 中,视图绑定生成了一个只有一个参数的 inflate 方法,该方法经过将 parent 设定为空值来指定当前视图不会绑定到父视图中;视图绑定也暴露了一个有三个参数的 inflate 方法,来让您在须要的时候传入 parent 和 attachToParent 参数。

真正神奇的地方是 bind 方法的调用。这里会填充视图并绑定全部的属性,同时作一些错误检测并生成清晰的错误提示。

@NonNull
 public static ActivityAwesomeBinding bind(@NonNull View rootView) {
    /* 编辑: 简化代码 – 真实状况下生成的代码是一个优化过的版本 */
    Button button = rootView.findViewById(R.id.button);
    TextView subtext = rootView.findViewById(R.id.subtext);
    TextView title = rootView.findViewById(R.id.title);
    if (button != null && subtext != null && title != null) {
      return new ActivityAwesomeBinding((ConstraintLayout) rootView, button, subtext, title);
    }
    throw new NullPointerException("Missing required view […]");
}

△ 自动生成的 bind 方法的简化版本

bind 是绑定对象中最复杂的一个方法,它经过调用 findViewById 来绑定每一个视图。既然编译器能够经过 XML 布局文件知道每一个属性的类型和为空的可能性,那他就能够安全的调用 findViewById。

请注意,视图绑定生成的真正的 bind 方法要来的更长,而且其中使用了一个标记 break 语句来优化字节码,您能够查看 Jake Wharton 撰写的这篇文章 来了解更多优化有关的内容。在每一个绑定对象中,都会暴露三个静态方法来建立绑定对象实例,下面是每一个方法使用场景的简要说明:

  • inflate(inflater) -- 在例如 Activity onCreate 方法里,这类没有父视图须要被传入的场合使用
  • inflate(inflater, parent, attachToParent) -- 在 Fragment 或 RecyclerView Adapter (或者说 ViewHolder 中) ,这类您须要传递父级 ViewGroup 给绑定对象时使用。
  • bind(rootView) -- 在您已经得到对应视图,而且只想经过视图绑定来避免使用 findViewById 时使用。这个方法在使用视图绑定改造和重构现有代码时很是有用。

对使用include标签引入的布局会发生什么影响

前面已经讲过,视图绑定会为 Module 下的每个布局文件生成一个绑定对象,这个说法在布局文件被另外一个布局文件使用 include 引入时依然适用。

<!-- activity_awesome.xml -->
<androidx.constraintlayout.widget.ConstraintLayout>
    <include android:id="@+id/includes" layout="@layout/included_buttons"
</androidx.constraintlayout.widget.ConstraintLayout>

<!-- included_buttons.xml -->
<androidx.constraintlayout.widget.ConstraintLayout>
    <Button android:id="@+id/include_me" />
</androidx.constraintlayout.widget.ConstraintLayout>

△ 视图绑定中使用 include 标签的示例
注意: include 标签下有一个 id。

在使用引入布局的时候,视图绑定会建立一个被引入布局绑定对象的引用。注意 include>标签有一个 id: android:id="@+id/includes"。这里的逻辑跟使用普通视图同样, include 标签也须要有一个 id 才能在绑定对象中生成对应的属性。

include 标签必须有一个 id,才能生成对应的属性。
public final class ActivityAwesomeBinding implements ViewBinding {
  ...

  @NonNull
  public final IncludedButtonsBinding includes;

视图绑定会在 ActivityAwesomeBinding 中生成一个 IncludedButtonsBinding 的引用。

结合数据绑定来使用视图绑定

视图绑定只是 findViewById 的取代方案,若是您但愿在 XML 中自动绑定视图,可使用 数据绑定 库。数据绑定和视图绑定能够生成一样的组件,它们能够同时工做。

在二者都被开启时,使用 <layout> 标签的布局会由数据绑定来生成绑定对象;而其他的布局则由视图绑定生成绑定对象。

您能够在同一 Module 中同时使用数据绑定和视图绑定。

咱们之因此开发视图绑定做为数据绑定的补充,是由于许多开发者反映说,但愿有一个轻量的解决方案,能在数据绑定以外替代 findViewById——视图绑定提供的正是这一功能。

视图绑定对比 Kotlin 合成方法与 ButterKnife

关于视图绑定,一个最多见的问题是: "我是否应该用视图绑定替代 Kotlin 合成方法或 ButterKnife ? " 两者都是目前十分红功的组件库,有许多应用使用它们解决 findViewById 的问题。

对于大多数应用来讲,咱们推荐尝试使用视图绑定来替代这两个库,由于视图绑定能够提供更加安全和准确的视图映射方式。

△ 视图绑定空安全、只引用当前布局中的视图、支持 Java 和 Kotlin,同时也更简洁

上图为对比视图绑定、ButterKnife 和 Kotlin 合成方法的功能。

虽然 ButterKnife 会在运行时校验可空与不可空,可是编译器并不会检查您匹配的视图是否在存在于您的布局之中。

为了安全性与更简洁代码,咱们推荐尝试使用视图绑定。

点击这里了解更多有关视图绑定的信息。

相关文章
相关标签/搜索