(译) Data Binding 生成的绑定类

原文链接

数据绑定库生成了用来访问布局的变量和视图的绑定类。这篇文章展示了如何创建以及自定义生成的绑定类。

生成的绑定类将布局变量与布局中的视图链接起来。绑定类的名称和包可以自定义。所有生成的绑定类都继承自 ViewDataBinding 类。

每个布局文件都会对应生成一个绑定类。默认情况下,类的名称是基于布局文件的名称的,将它转换为驼峰形式并在加上 Binding 后缀。上述文件的名是 activity_main.xml ,所以对应生成的类是 ActivityMainBinding 。这个类持有了所有从布局属性(例如,user 变量)到布局视图的绑定,以及知道如何为绑定表达式赋值。

创建一个绑定对象

绑定对象应该在布局生成之后就被创建,从而确保布局中在绑定对象使用表达式绑定到视图之前,视图层没有被修改。绑定对象到布局的最常见方式,是使用绑定类的静态方法。你可以生成视图层,然后使用 binding 类的 inflate() 方法把对象绑定到它身上,如下例所示:

1
2
3
4
5
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)
}

又一个 inflate() 方法的替代版本,除了 LayoutInflater 对象之外,添加了一个 ViewGroup 对象,如下例所示:

1
val binding: MyLayoutBinding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false)

如果布局 inflate 的时候 使用了不同的机制,就可以分别捆绑,如下:

1
val binding: MyLayoutBinding = MyLayoutBinding.bind(viewRoot)

有时候绑定类型是不能提前知道的。在这种情况下,绑定可以使用 DataBindingUtils 进行创建,如以下的代码片段所示:

1
2
val viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent)
val binding: ViewDataBinding? = DataBindingUtil.bind(viewRoot)

如果你在 Fragment, ListView 或者 RecyclerView adapter 内部使用数据绑定,你可能会想要使用绑定类的 inflate() 方法或是 DataBindingUtil 类,如下例所示:

1
2
3
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// 或者
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

带有ID的视图

数据绑定库为布局中每一个有 ID 的视图在绑定类中创建了一个不可改变的值。例如,数据绑定库创建了 TextView 类型的 firstNamelastName ,如下布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:id="@+id/lastName"/>
</LinearLayout>
</layout>

数据一次性地就能将包含 id 的视图从视图层中分离出来。这个机制比对每个布局中的视图调用 findViewById() 方法快很多。

id 在没有数据绑定的时候不是必须的,但是还是存在一些从代码访问视图的必要情形。

变量

数据绑定类为每个在布局中定义的变量都生成了存取方法。例如,以下的布局在绑定类中就生成了针对 user , image 以及 ``note 变量的绑定类。

1
2
3
4
5
6
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>

viewStubs

不像正常的视图,ViewStub 对象开始的时候不是一个可见的对象。当它被设置成可见或明确地要去生成的时候,它们通过 inflate 其他布局的方式来取代它们自己。

因为 ViewStub 基本上会从视图层上消失,绑定对象的视图也必须消失从而才能被垃圾回收认领。因为视图是 final的,ViewStubProxy 对象取代了 ViewStub 在生成绑定类中的位置,当 ViewStub 存在的时候给予你访问的权限同时也给予你当 ViewStub 被填充时填充视图的权限。

当填充另一个布局的时候,必须建立为新的布局建立绑定,因此,ViewStubProxy 必须监听 ViewStub onInflateListener 并且在必要的时候建立绑定。因为同一时间只能存在一个listener, ViewStubProxy 允许你设置一个 OnInflateListener,它会在建立连接后调用。

立即绑定

但变量或者可观察对象发生改变的时候,绑定时被安排到下一帧之前改变的。但是很多情况下,绑定必须被立即执行。为了能强制执行,就要使用 executePendingBindings() 方法。

高级绑定

动态变量

很多时候,绑定类是不确定的。例如,RecyclerView.Adapter 操作着不同的布局并没有确定的绑定类。但是它仍旧必须在调用 onBindViewHolder 的时候完成绑定值的赋值。

在以下的例子中,所有 RecyclerView 所绑定的布局都有着一个 item 变量。 BindingHolder 对象有着一个返回 ViewDataBinding 基础类的 getBinding() 方法。

1
2
3
4
5
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
item: T = items.get(position)
holder.binding.setVariable(BR.item, item);
holder.binding.executePendingBindings();
}

注意 DataBinding 库会在 module 包中生成一个叫 BR 的类,它包含了用于数据绑定的资源的 id。在上例中,库自动生成了BR.item变量。

后台线程

只要它不是一个集合,你就可以在后台线程中改变你的数据模型。在评估期间数据绑定本地化了每个变量/域来避免所有的并发问题。

自定义绑定类名称

默认情况下,绑定类是基于布局文件的名称生成的,以大写字母开头,移除了下划线,将之后的单词的首字母大写,并添加Binding 这个词。这个类被放置在 module 包的 databing 包下。例如,布局文件 contact_item_xml 就生成了 ContactItemBinding 类。如果module 包是com.example.my.app,那么绑定就会被放在 com.example.my.app.databinding 包中。

绑定类可以通过调整 data 元素的 class 属性的方式被重命名并放置在不同的包下。例如,布局文件contact_item_xml 在当前module的 databinding 包中生成了 ContactItem 类:

<data class="ContactItem"></data>

你可以通过在类名钱添加句号的方式把绑定类生成在不同的包中。下例生成的绑定类就在module的包中:

<data class=".ContactItem"></data>

对于你想要生成包名的地方,你也可以使用完整的包名。以下就在com.example包中创建了 ContactItem 绑定类:

<data class="com.example.ContactItem"></data>
非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号