数据绑定布局
数据绑定布局文件以根标记layout
开头,后跟data
元素和view
根元素。以下代码展示了示例布局文件:
<?xml version="1.0" encoding="utf-8"?>
<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}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
数据对象
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
可观察性
可观察性是指一个对象将其数据变化通知给其他对象的能力。通过数据绑定库,您可以让对象、字段或集合变为可观察。
当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。
可观察字段
在创建实现Observable
接口的类时要完成一些操作,但如果您的类只有少数几个属性,则这样操作的意义不大。在这种情况下,您可以使用通用Observable
类和以下特定于基元的类,将字段设为可观察字段:ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable
可观察字段是具有单个字段的自包含可观察对象。原语版本避免在访问操作期间封箱和开箱。要使用此机制,请采用 Java 编程语言创建 public final
属性,或在 Kotlin 中创建只读属性,如以下示例所示:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
- 可观察观察集合
某些应用使用动态结构来保存数据。可观察集合允许使用键访问这些结构。当键为引用类型(如String
)时,ObservableArrayMap
类非常有用,如以下示例所示:
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在布局中,可使用字符串键找到地图,如下所示:
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{String.valueOf(1 + (Integer)user.age)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
当键为整数时,ObservableArrayList
类非常有用,如下所示:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在布局中,可通过索引访问列表,如以下示例所示:
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- 可观察对象
实现Observable
接口的类允许注册监听器,以便它们接收有关可观察对象的属性更改的通知。
Observable
接口具有添加和移除监听器的机制,但何时发送通知则必须由您决定。为便于开发,数据绑定库提供了用于实现监听器注册机制的BaseObservable
类。实现BaseObservable
的数据类负责在属性更改时发出通知。具体操作过程是向 getter 分配Bindable
注释,然后在 setter 中调用notifyPropertyChanged()
方法,如以下示例所示:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
绑定数据
系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。建议的绑定创建方法是在扩充布局时创建,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}
或者,您可以使用LayoutInflater
获取视图,如以下示例所示:
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
如果您要在Fragment
,ListView
或RecyclerView
适配器中使用数据绑定项,您可能更愿意使用绑定类或DataBindingUtil
类的inflate()
方法,如以下代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
方法引用、监听绑定
方法引用
和监听器绑定
之间的主要区别在于实际监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果您希望在事件发生时对表达式求值,则应使用监听器绑定。
- 方法引用
在方法引用中,方法的参数必须与事件监听器的参数匹配。
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
绑定表达式可将视图的点击监听器分配给 onClickFriend() 方法,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<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:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
- 监听绑定
在监听器绑定中,只有您的返回值必须与监听器的预期返回值相匹配(预期返回值无效除外)。例如,请参考以下具有 onSaveClick() 方法的 presenter 类:
public class Presenter {
public void onSaveClick(Task task){}
}
然后,您可以将点击事件绑定到 onSaveClick() 方法,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
绑定适配器
数据绑定库允许您通过使用适配器指定为设置值而调用的方法、提供您自己的绑定逻辑,以及指定返回对象的类型。
- 自动选择方法
对于名为 example
的特性,库自动尝试查找接受兼容类型作为参数的方法 setExample(arg)
。系统不会考虑特性的命名空间,搜索方法时仅使用特性名称和类型。
以 android:text="@{user.name}"
表达式为例,库会查找接受 user.getName()
所返回类型的 setText(arg)
方法。如果 user.getName()
的返回类型为 String
,则库会查找接受 String
参数的 setText()
方法。如果表达式返回的是 int
,则库会搜索接受 int
参数的 setText()
方法。表达式必须返回正确的类型,您可以根据需要强制转换返回值的类型。
即使不存在具有给定名称的特性,数据绑定也会起作用。然后,您可以使用数据绑定为任何 setter 创建特性。例如,支持类DrawerLayout
没有任何特性,但有很多 setter。以下布局会自动将setScrimColor(int)
和setDrawerListener(DrawerListener))
方法分别用作 app:scrimColor
和 app:drawerListener
特性的 setter:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
- 指定自定义方法名称
某些特性拥有名称不符的 setter。在这些情况下,某个特性可能会使用BindingMethods
注释与 setter 相关联。注释与类一起使用,可以包含多个BindingMethod
注释,每个注释对应一个重命名的方法。绑定方法是可添加到应用中任何类的注释。在以下示例中,android:tint
特性与setImageTintList(ColorStateList)
方法相关联,而不与 setTint()
方法相关联:
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
大多数情况下,您无需在 Android 框架类中重命名 setter。特性已使用命名惯例实现,可自动查找匹配的方法。
- 提供自定义逻辑
某些特性需要自定义绑定逻辑。例如,android:paddingLeft
特性没有关联的 setter,而是提供了 setPadding(left, top, right, bottom)
方法。使用BindingAdapter
注释的静态绑定适配器方法支持自定义特性 setter 的调用方式。
Android 框架类的特性已经创建了 BindingAdapter
注释。例如,以下示例展示了 paddingLeft
特性的绑定适配器:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
参数类型非常重要。第一个参数用于确定与特性关联的视图类型,第二个参数用于确定在给定特性的绑定表达式中接受的类型。
您还可以使用接收多个特性的适配器。如果 ImageView
对象同时使用了 imageUrl
和 error
,并且 imageUrl
是字符串,error
是Drawable
,则会调用适配器。
如以下示例所示:
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.get().load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
如果您希望在设置了任意特性时调用适配器,则可以将适配器的可选 requireAll
标记设置为 false
,如以下示例所示:
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
if (url == null) {
imageView.setImageDrawable(placeholder);
} else {
MyImageLoader.loadInto(imageView, url, placeholder);
}
}