修改 build.gradle 文件:
android {
...
dataBinding {
enabled = true
}
}
当引用一个开启了 DataBinding 配置的 Module 时,主 Module 也要开启 DataBinding 配置。
<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>
LinearLayout 标签内的是原始的 xml 布局,使用 DataBinding 时,需要在它外面再套一层 layout
标签,再给 layout
标签内添加一个 data
标签。在 data
标签内就可以通过 variable
标签声明 view 可引用的对象,即 layout 的属性。
如上述代码声明了 user 对象,在 TextView 中就可以通过 @{}
引用它的属性 @{user.firstName}
。
快速转化:在根 ViewGroup 上,按 alt + 回车,就可以将该布局 convert to data binding layout
。
在 variable
标签中声明的对象需要添加 name、type 属性。name 就是名字,type 是类名,java.lang 中的类可以直接写类名称,如 String,其他类需要写类全名,如 com.example.User。
要访问到一个对象的属性,可以使用以下三种方式。DataBinding 会按 firstName、getFirstName()、firstName() 的顺序去寻找值。
// public
public class User {
public String firstName;
public String lastName;
}
// get属性名() 方法
public class User {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
// 属性名() 方法
public class User {
private String firstName;
private String lastName;
public String firstName() {
return firstName;
}
public String lastName() {
return lastName;
}
}
每个布局文件都会产生一个 binding 类,默认类名是布局文件名去除下划线再加上 Binding 后缀,如 activity_main.xml
的 binding 类是 ActivityMainBinding
。存放于 /app/build/generated/source/apt/debug/.../databinding
目录下。(修改类名见 三)
推荐的方式是在 inflating layout 的时候去创建 binding,如下所示:
@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);
}
当在 Fragment、ListView/RecyclerView Adapter 中使用时,可以使用 inflate 方法:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
binding 表达式可以使用 java 中的几乎所有语法(除了 this、super、new、泛型调用等)。
另有一些特殊语法,如下所示:
非空判断符号 ??
android:text="@{user.displayName ?? user.lastName}"
相当于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
非空检查
binding 表达式取值时会进行非空判断,如果为空,则不进行下一步取值。
如:
android:text="@{String.valueOf(user.name)}
,如果 user 为 null,显示 null
android:text="@{String.valueOf(user.age)}
,如果 user 为 null,显示 0
集合
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
为了使 xml 的语法保持正确,不能使用左尖括号,如 List
要写作 List<String>
。
另外,@{map[key]}
可写作 @{map.key}
。
字面值
在 @{} 中使用字面值,取决于 @{} 外部是什么。
@{} 外部是 ‘’,则用 “” 来包围字面值。
@{} 外部是 “”,则用 `` 来包围字面值。
android:text='@{"firstName"}'
或 android:text="@{`firstName`}"
资源
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
import
import 可以使得在 binding 表达式、variable 标签中,可以引用某个类中的类方法、类变量。
如
<data>
<import type="android.view.View"/>
data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
在 visibility 的 binding 表达式中,使用了 View.VISIBLE 和 View.GONE。如果不添加 import,是无法编译成功的。
如
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
data>
在 variable 的 type 中也可以直接使用 import 的类,而不用再引用全名。
别名
当 import 两个同名的类时,可以使用别名对它们进行区分。
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
Variables
variable 标签可以声明 binding 表达式中可使用的变量。
<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>
如果一个布局文件有多种配置(竖屏、横屏等),它们里面的 variable 会被合并,所以不能有冲突。
每个 variable 都会默认生成 getter、setter 方法。
每个 layout 文件都会自带一个 context 对象,它来自根 View 的 getContext() 方法。
Includes
binding 支持 include 标签,可以使用 bind
给 include 的布局传递 variable,如下所示:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
LinearLayout>
layout>
但是不支持给 merge 标签加 layout 外壳。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
data>
<merge>
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
merge>
layout>
有两种方式可以进行事件处理:方法引用和监听器绑定
方法引用
事件可以直接绑定到处理器的方法上。
public class MyHandlers {
public void onClickFriend(View view) { ... }
}
<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>
这和原生的 android:onClick="..."
的区别在于,原生的 onClick 方法只能在 Activity 中创建,且没有编译期的检查。binding 表达式 的 onClick 可以指定任意对象,并有编译期的检查。
监听器绑定
方法绑定只能把 View 传给某个方法,这个方法的参数、返回值都必须与事件监听器所需的一样。
监听器绑定可以使用任何的 binding 表达式,即它实现的是方法的内容。所以参数不一定跟事件监听器所需的一样,只需返回值一样即可。
如:
public class Presenter {
public void onSaveClick(Task task){}
}
<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>
这里的 () -> presenter.onSaveClick(task)
相当于监听器方法,而 presenter.onSaveClick(task)
已经是监听器方法里的实现。
所以也可以直接这样:
<TextView
...
android:onClick="@{(v) -> v.setVisibility(View.GONE)}"
.../>
但是尽量不要把复杂的逻辑放在 binding 表达式中,这会带来阅读和维护的成本。
响应式对象,指的是,对象数据变化后,会通知其他对象自己的修改,从而执行一些特定逻辑,如 UI 的变更。
要实现响应式的效果,数据对象必须实现 Observable 接口(DataBinding 库中的接口),但对于一个简单的类这么做有点过于复杂,所以 DataBinding 库提供了一系列 ObservableXXX
类,如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、ObservableParcelable、ObservableField<> 等。
它们使用 get()、set() 来取值和赋值。
实例:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
<TextView
...
android:text="@{user.firstName}"
.../>
现在,当我们调用 user.firstName.set("Google")
,TextView 就会显示 Google。而不用再去调一遍 setText()。
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" />
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/>
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 接口的类就拥有了响应式的能力。为了方便开发,DataBinding 库提供了一个 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);
}
}
BR 类位于 /app/build/generated/source/apt/debug/.../BR.java
,里面包含 DataBinding 所需的资源 id。Bindable 注解在编译时,会往 BR 文件中添加一个条目。
如果一个类的基类不能变动的话,可以使用 PropertyChangeRegistry 来实现响应式的功能。
binding 类用来访问 View 和 Variable,每个布局文件都对应一个 binding 类。binding 类都继承于 ViewDataBinding
类。
生成绑定对象
对于 Activity:
// 第一种方式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_my);
}
// 第二种方式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
// 第三种方式
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewGroup contentView = findViewById(android.R.id.content);
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), contentView, true);
}
对于 Fragment、ListView/RecyclerView Adapter :
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
// or
View viewRoot = LayoutInflater.from(this).inflate(layoutId, parent, attachToParent);
ListItemBinding binding = DataBindingUtil.bind(viewRoot);
带 ID 的 View
bindind 类中会自动生成所有带 id 的 View 对象,并将其设为 final。这比每次都通过 findViewById() 来找 View 要快多了。
<TextView
android:id="@+id/tv_hello"
.../>
生成的 binding 类:
public class ActivityMvvmHelloBinding {
public final android.widget.TextView tvHello;
...
}
访问:
binding.tvHello.setText("...");
ViewStubs
在 binding 类中,布局文件中的 ViewStub 会用 ViewStubProxy 来表示。ViewStubProxy 可以代表 ViewStub inflate 前和后的两种状态,inflate 之前能访问到 ViewStub 对象,inflate 之后能访问到 inflate 的 View,如果这个 View 有 binding 配置,那 ViewStubProxy 能获取到相应的 binding 类。
布局中的 ViewStub:
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
在生成的 binding 类中:
public final android.databinding.ViewStubProxy viewStub;
立即绑定
当一个 variable 或 observable 对象改变后,数据绑定会在下一帧进行,如果需要立即执行的话,可以使用 executePendingBindings() 方法。
动态变量
有时,binding 类的类别是未知的,比如在 RecyclerView.Adapter 中有多种 ViewHolder 时。这时可以使 ViewHolder 返回一个 ViewDataBinding,然后调用 setVariable() 方法进行数据绑定。
setVariable 的第一个参数是 variableId,它保存在 BR 文件中,编译时生成。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = items.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
...
class BindingHolder extends RecyclerView.ViewHolder {
ViewDataBinding binding;
public HelloViewHolder(View itemView) {
super(itemView);
binding = ItemRcvHelloBinding.bind(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
}
自定义 binding 类名
默认类名是布局文件名去除下划线再加上 Binding 后缀,如 activity_main.xml
的 binding 类是 ActivityMainBinding
。
通过 data 标签的的 class 属性可以自定义文件名,同时放置到不同的文件夹。
<data class="com.example.ContactItem">
…
data>
但这样的做法在 AndroidStudio 3.4 上好像有问题,会报 Attribute class is not allowed here
的错。
自动方法选择
<data>
<variable
name="contentText"
type="String" />
data>
<TextView
android:text="@[contentText]"
.../>
当调用 binding.setContentText("hello")
时,binding 内部会找到引用它的 View(这里是 TextView),和引用它的属性(这里是 text),然后去找该属性的 setter 方法(这里是 setText(String) 方法),如果没找到就会报错。
如果一个 View 的标签不支持某个属性,但是有 setter 方法,也可以这么设置。
如一个自定义
public class MyTextView extends TextView {
...
public void setContentText(String text) {
System.out.println("exec setContentText with " + text);
}
}
<MyTextView
android:text="@[contentText]"
app:contentText="@[contentText]"
.../>
现在调用 binding.setContentText("hello")
,那 MyTextView 的 setText()、setContentText() 方法都会被调用。
指定一个自定义方法
有时候一些属性和方法名不是一一对应的,这时候可以使用 BindingMethods
、BindingMethod
注解来指定对应关系。
一些系统原生的方法不用这样设置,DataBinding 内部已经做了处理,如 android:tint
属性和 setImageTintList(ColorStateList)
方法。
@BindingMethods({
@BindingMethod(type = MyTextView.class,
attribute = "contentText",
method = "setContent")
})
public class MyTextView extends TextView {
...
public void setContent(String text) {
content = text;
}
}
或者放入一个单独的 Adapter 类:
@BindingMethods({
@BindingMethod(type = MyTextView.class,
attribute = "contentText",
method = "setContent")
})
public class MyTextViewBindingAdapter {
}
提供自定义逻辑
上面的效果页可以使用另一种方式实现:
public class MyTextView extends TextView {
...
@BindingAdapter("contentText")
public static void setContent(View view, String text) {
content = text;
}
}
或者放入一个单独的 Adapter 类:
public class MyTextView extends TextView {
...
public void setContent(String text) {
content = text;
}
}
public class MyTextViewBindingAdapter {
@BindingAdapter("contentText")
public static void setContent(MyTextView view, String text) {
view.setContent(text);
}
}
BindingAdapter
注解相比 BindingMethods
的好处在于可以支持多个参数的同时设置:
@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);
}
}
当我们自定义的 BindingAdapter 与 DataBinding 库默认生成的 BindingAdapter 有冲突时,会采用我们自定义的 BindingAdapter。
如 View 的 setPaddingLeft 方法,默认 BindingAdapter 的实现是这样:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
如果我们定义了自己的 setPaddingLeft,那就会覆盖默认的。
方法的旧值和新值
如果要在设置新值的时候同时使用旧值,可以在 BindingAdapter 方法中依次传入所有旧值,再依次传入所有新值。
如:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
又如:
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) {
view.removeOnLayoutChangeListener(oldValue);
}
if (newValue != null) {
view.addOnLayoutChangeListener(newValue);
}
}
}
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
当 handler 改变时,setOnLayoutChangeListener 方法就会被调用,移除旧 listener,添加新 listener。
自动对象转换
当 binding 表达式返回的值是一个 Object 类型时,DataBinding 库会将其转化为属性的 setter 方法需要的参数类型。
如 userMap[“lastName”] 返回一个 Object:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
DataBinding 库会找到 setText() 方法,setText() 方法需要一个 CharSequence 类型的参数,所以 userMap[“lastName”] 会被转化为 CharSequence。
当然这需要该 Object 本来就是一个 CharSequence,否则运行时会抛出强转异常。
如果 binding 表达式返回的不是一个 Object,而是一个特定类型的值,那如果类型不符合,编译就不会通过。
自定义转换
要解决这种编译不通过的问题,可以设置 BindingConversion 方法。
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
但 binding 表达式返回值的值必须是一致的,不能像这样,可能返回 drawable,也可能发挥 color:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
相比于 Observable 对象,LiveData 的好处在于它知道订阅者的生命周期。在 Android Studio 3.1 及以后,你可以把 Observable Fields 替换为 LiveData。
要使用 LiveData,需要指定 LiveData 的 Lifecycle Owner。
class ViewModelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Inflate view and obtain an instance of the binding class.
UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this);
}
}
DataBinding 库和 ViewModel 库可以无缝衔接。
要使用 ViewModel 组件,需要先创建 ViewModel,然后将 ViewModel 对象作为 binding 类的一个属性。
class ViewModelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// Obtain the ViewModel component.
UserModel userModel = ViewModelProviders.of(getActivity())
.get(UserModel.class);
// Inflate view and obtain an instance of the binding class.
UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
// Assign the component to a property in the binding class.
binding.viewmodel = userModel;
}
}
在 layout 文件中可以使用 ViewModel 对象。
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />
有时更适合使用实现了Observable 接口的 ViewModel,而不是 LiveData。这样能够提供更多的控制性。
我们可以实现自己的订阅、通知逻辑,通过 addOnPropertyChangedCallback
添加订阅者,通过 removeOnPropertyChangedCallback
移除订阅者,通过 notifyChange
、notifyPropertyChanged
通知订阅者。
/**
* A ViewModel that is also an Observable,
* to be used with the Data Binding Library.
*/
class ObservableViewModel extends ViewModel implements Observable {
private PropertyChangeRegistry callbacks = new PropertyChangeRegistry();
@Override
protected void addOnPropertyChangedCallback(
Observable.OnPropertyChangedCallback callback) {
callbacks.add(callback);
}
@Override
protected void removeOnPropertyChangedCallback(
Observable.OnPropertyChangedCallback callback) {
callbacks.remove(callback);
}
/**
* Notifies observers that all properties of this instance have changed.
*/
void notifyChange() {
callbacks.notifyCallbacks(this, 0, null);
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
void notifyPropertyChanged(int fieldId) {
callbacks.notifyCallbacks(this, fieldId, null);
}
}
使用单向数据绑定,可以通过数据设置 View 的内容。
使用双向数据绑定,View 的属性也可以反过来更新数据的值。
双向数据绑定的 binding 表达式使用 @={}
。同时数据类要实现 Observable 接口,或继承 BaseObservable 类,也可以直接使用 ObservableInt、ObservableField<> 等类。
如:
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>
public class LoginViewModel extends BaseObservable {
// private Model data = ...
@Bindable
public Boolean getRememberMe() {
return data.rememberMe;
}
public void setRememberMe(Boolean value) {
// Avoids infinite loops.
if (data.rememberMe != value) {
data.rememberMe = value;
// React to the change.
saveData();
// Notify observers of a new value.
notifyPropertyChanged(BR.remember_me);
}
}
}
数据-View:
数据类通过 setRememberMe 更新数据,并通知 View(通过 notifyPropertyChanged 通知),View 通过 getRememberMe 获取数据的值。
View-数据:
View 通过 setChecked() 更新内容,并通知数据类(通过 InverseBindingListener.onChange() 通知),数据类通过通过 View 的 isChecked() 方法获取数据的值。
CheckBox 内容的更新由 CheckBox 自己的监听器 OnCheckedChangeListener 来监听,在 OnCheckedChangeListener 的回调里调用 InverseBindingListener.onChange()。
如 MyView 有一个 自定义属性 time。
用数据给 View 设置内容:
@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
// Important to break potential infinite loops.
if (view.time != newValue) {
view.time = newValue;
}
}
从 View 的内容获取数据:
@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
return view.getTime();
}
当数据改变时,会调用 BindingAdapter
注解的方法,当 View 内容改变时,需要一个 InverseBindingListener 来调 onChange() 方法,从而通知 Model 进行修改。
InverseBindingListener 由 DataBinding 库自动生成,但 onChange() 方法需要我们自己调用。
获取 InverseBindingListener 对象,需要声明一个 @BindingAdapter 方法,参数为 app/android:属性名AttrChanged
。(app: 可省略,android: 不确定,还未验证)
@BindingAdapter("app:timeAttrChanged")
public static void setListeners(
MyView view, final InverseBindingListener attrChange) {
// Set a listener for click, focus, touch, etc.
}
获取 InverseBindingListener 对象后,就可以在 View 自己的内容变化监听器里去调 InverseBindingListener.onChange()
方法,然后从 InverseBindingAdapter
注解方法中获取 View 内容代表的数据,从而更新数据。
如果传递给 View 的数据需要做一些处理,可以编写一个 Converter 类来处理。
<EditText
android:id="@+id/birth_date"
android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>
import Converter 类后,就可以使用 dataToString 方法。
public class Converter {
public static String dateToString(EditText view, long oldValue,
long value) {
// Converts long to String.
}
}
但是因为使用的是双向绑定 @={}
,还需要提供一个逆绑定方法,通过 @InverseMethod
标识。
public class Converter {
@InverseMethod("stringToDate")
public static String dateToString(EditText view, long oldValue,
long value) {
// Converts long to String.
}
public static long stringToDate(EditText view, String oldValue,
String value) {
// Converts String to long.
}
}
因为双向绑定的存在,可能会出现无限循环的情况,要注意新值和旧值的判断,相等时不再往下执行。