视图绑定   Android Jetpack 的一部分。

视图绑定 是一项功能,可以简化编写与视图交互的代码。在模块中启用视图绑定后,它会为该模块中存在的每个 XML 布局文件生成一个绑定类。绑定类的实例包含对布局中具有 ID 的所有视图的直接引用。

在大多数情况下,视图绑定替换 findViewById

设置

视图绑定是在模块级别启用的。要在模块中启用视图绑定,请在模块级 build.gradle 文件中将 viewBinding 构建选项设置为 true,如以下示例所示

Groovy

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

Kotlin

android {
    ...
    buildFeatures {
        viewBinding = true
    }
}

如果要忽略布局文件,使其在生成绑定类时不被包含,请将 tools:viewBindingIgnore="true" 属性添加到该布局文件的根视图

<LinearLayout
        ...
        tools:viewBindingIgnore="true" >
    ...
</LinearLayout>

用法

如果为模块启用了视图绑定,则会为该模块包含的每个 XML 布局文件生成一个绑定类。每个绑定类都包含对根视图和所有具有 ID 的视图的引用。绑定类的名称是通过将 XML 文件的名称转换为 PascalCase,并在末尾添加“Binding”生成的。

例如,假设有一个名为 result_profile.xml 的布局文件,其中包含以下内容

<LinearLayout ... >
    <TextView android:id="@+id/name" />
    <ImageView android:cropToPadding="true" />
    <Button android:id="@+id/button"
        android:background="@drawable/rounded_button" />
</LinearLayout>

生成的绑定类名为 ResultProfileBinding。此类有两个字段:一个名为 nameTextView 和一个名为 buttonButton。布局中的 ImageView 没有 ID,因此绑定类中没有对它的引用。

每个绑定类还包含一个 getRoot() 方法,该方法提供对相应布局文件的根视图的直接引用。在本例中,ResultProfileBinding 类中的 getRoot() 方法返回 LinearLayout 根视图。

以下部分演示了在活动和片段中使用生成的绑定类。

在活动中使用视图绑定

要在活动中使用绑定类,请在活动的 onCreate() 方法中执行以下步骤

  1. 调用生成的绑定类中包含的静态 inflate() 方法。这将创建绑定类的实例,供活动使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法 获取对根视图的引用。
  3. 将根视图传递给 setContentView(),使其成为屏幕上的活动视图。

以下示例显示了这些步骤

Kotlin

private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setContentView(view)
}

Java

private ResultProfileBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    binding = ResultProfileBinding.inflate(getLayoutInflater());
    View view = binding.getRoot();
    setContentView(view);
}

现在可以使用绑定类的实例引用任何视图

Kotlin

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

Java

binding.name.setText(viewModel.getName());
binding.button.setOnClickListener(new View.OnClickListener() {
    viewModel.userClicked()
});

在片段中使用视图绑定

要在片段中使用绑定类,请在片段的 onCreateView() 方法中执行以下步骤

  1. 调用生成的绑定类中包含的静态 inflate() 方法。这将创建绑定类的实例,供片段使用。
  2. 通过调用 getRoot() 方法或使用 Kotlin 属性语法 获取对根视图的引用。
  3. onCreateView() 方法返回根视图,使其成为屏幕上的活动视图。

Kotlin

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

Java

private ResultProfileBinding binding;

@Override
public View onCreateView (LayoutInflater inflater,
                          ViewGroup container,
                          Bundle savedInstanceState) {
    binding = ResultProfileBinding.inflate(inflater, container, false);
    View view = binding.getRoot();
    return view;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    binding = null;
}

现在可以使用绑定类的实例引用任何视图

Kotlin

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

Java

binding.name.setText(viewModel.getName());
binding.button.setOnClickListener(new View.OnClickListener() {
    viewModel.userClicked()
});

为不同的配置提供提示

当您在多个配置中声明视图时,有时根据特定布局使用不同的视图类型是有意义的。以下代码片段显示了这方面的示例

# in res/layout/example.xml

<TextView android:id="@+id/user_bio" />

# in res/layout-land/example.xml

<EditText android:id="@+id/user_bio" />

在这种情况下,您可能希望生成的类公开一个名为 userBio 的字段,其类型为 TextView,因为 TextView 是公共基类。由于技术限制,视图绑定代码生成器无法确定这一点,而是生成了一个 View 字段。这需要稍后使用 binding.userBio as TextView 来强制转换该字段。

为了解决此限制,视图绑定支持 tools:viewBindingType 属性,让您可以告诉编译器在生成的代码中使用什么类型。在前面的示例中,您可以使用此属性使编译器将字段生成为 TextView

# in res/layout/example.xml (unchanged)

<TextView android:id="@+id/user_bio" />

# in res/layout-land/example.xml

<EditText android:id="@+id/user_bio" tools:viewBindingType="TextView" />

在另一个示例中,假设您有两个布局,一个包含 BottomNavigationView,另一个包含 NavigationRailView。这两个类都扩展了 NavigationBarView,其中包含大多数实现细节。如果您的代码不需要知道当前布局中是否存在哪个子类,则可以使用 tools:viewBindingType 将生成的类型设置为 NavigationBarView,无论布局如何

# in res/layout/navigation_example.xml

<BottomNavigationView android:id="@+id/navigation" tools:viewBindingType="NavigationBarView" />

# in res/layout-w720/navigation_example.xml

<NavigationRailView android:id="@+id/navigation" tools:viewBindingType="NavigationBarView" />

视图绑定无法在生成代码时验证此属性的值。为了避免编译时和运行时错误,该值必须满足以下条件

  • 该值必须是继承自 android.view.View 的类。
  • 该值必须是它所放置的标签的超类。例如,以下值不起作用

      <TextView tools:viewBindingType="ImageView" /> <!-- ImageView is not related to TextView. -->
      <TextView tools:viewBindingType="Button" /> <!-- Button is not a superclass of TextView. -->
    
  • 最终类型必须在所有配置中始终如一地解析。

与 findViewById 的区别

视图绑定与使用 findViewById 相比具有重要的优势

  • 空安全:由于视图绑定创建对视图的直接引用,因此由于无效视图 ID 而导致的空指针异常的风险不存在。此外,当视图仅出现在布局的某些配置中时,包含其引用的绑定类中的字段将标记为 @Nullable
  • 类型安全:每个绑定类中的字段具有与其在 XML 文件中引用的视图匹配的类型。这意味着没有发生类转换异常的风险。

这些差异意味着布局和代码之间的不兼容会导致您的构建在编译时而不是运行时失败。

与数据绑定的比较

视图绑定和 数据绑定 都生成绑定类,您可以使用这些类直接引用视图。但是,视图绑定旨在处理更简单的用例,并且与数据绑定相比提供了以下优势

  • 更快的编译:视图绑定不需要任何注释处理,因此编译速度更快。
  • 易于使用:视图绑定不需要特别标记的 XML 布局文件,因此在您的应用程序中更快地采用。在模块中启用视图绑定后,它会自动应用于该模块的所有布局。

另一方面,与数据绑定相比,视图绑定具有以下限制

考虑到这些因素,在某些情况下,最好在项目中同时使用视图绑定和数据绑定。您可以在需要高级功能的布局中使用数据绑定,并在不需要高级功能的布局中使用视图绑定。

其他资源

要了解有关视图绑定的更多信息,请参阅以下其他资源

示例

博客

视频