绑定事件处理程序
DataBinding的一个关键就是View和ViewModel可以关联在一起,实现双向交互。例如在View层触发了一个事件,例如点击onClick,DataBinding可以在layout布局文件中的属性@{ }表达式中来处理这个事件,而不用自己去设置监听器来处理事件。例如,View.onClickListener中有一个方法是onClick,所以在view元素节点上有对应的属性android:onClick。
在DataBinding支持库中,有两种方式去处理事件。
1.方法引用:在DataBinding表达式中,你可以直接调用声明的对象的方法,但是这个方法的方法签名也就是参数列表必须和对应的监听器内包含的方法的方法签名一样。当表达式判断出属性值是对象的方法引用时,DataBinding包装这个对象和方法到一个新的监听器listener对象中,并在当前view中设置这个listener。如果表达式中判断是方法引用是null,那么DataBinding并不会创建一个监听器,而是设置一个null监听器。
2.监听器绑定:这个表达式使用的是lambda表达式,当事件发生时,这个表达式就会被DataBinding计算。DataBinding总会创建一个新的监听器,并在当前view设置这个监听器。当View的事件分发的时候,这个监听器就会计算lambda表达式,也就是调用lambda表达式。
这两种方式的区别就是方法引用是在数据绑定的时候就创建实际的监听器并绑定到View,而监听器绑定是在事件被触发的时候,实时计算表达式然后去处理事件。监听器绑定方法在事件发生时才运算Binding表达式,所以可以运行任意数据绑定表达式。在方法引用中,方法参数列表必须和监听器中方法的参数列表一致。而在监听器绑定方法中并不要求,而要求方法的返回值必须和监听器中方法的返回值类型相同。这两种方法的效果是一样的。下面举例说明:
首先在MainActivity中声明一个方法onClick,必须带有View参数:
@Override
protected void initView() {
binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
binding.setMainActivity(this);
binding.setMainHandler(new MainHandler());
}
public void onClick(View view){
Toast.makeText(this, "Click Me!!!", Toast.LENGTH_SHORT).show();
people.setFirstName("222222");
}
然后在layout文件中声明mainActivity变量,在上面的setMainActivity中初始化:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="mainActivity"
type="view.MainActivity"/>
<variable
name="mainHandler"
type="module.MainHandler"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@drawable/bg_main_btn"
android:text="Click me"
android:onClick="@{mainActivity::onClick}"/>
</LinearLayout>
</layout>
这样就点击button按钮就可以直接调用MainActivity中的onClick方法了。
下面举例监听器绑定的方法。我们可以再分离activity中的代码,我们可以创建一个model事件处理模块MainHandler,例如:
public class MainHandler {
public void showToast(Context context, String str){
Toast.makeText(context,str,Toast.LENGTH_SHORT).show();
}
}
layout中button修改为如下:
<Button
android:layout_marginTop="10dp"
android:layout_width="100dp"
android:layout_height="60dp"
android:background="@drawable/bg_main_btn"
android:text="Click me"
android:onClick='@{()->mainHandler.showToast(context,"click two")}'/>
//或者android:onClick='@{(view)->mainHandler.showToast(context,"click two")}'
这里使用了 lambda表达式( )->,其中( )可以传递view参数,也可以不传递。监听器绑定方法提供了两种选择,一种是忽略所有参数也就是不传递参数,另一种是显式传递所有参数。这个表达式等价于下面这个例子:
new View.OnClickListener() {
@Override
public void onClick(View v) {
//其中这个参数v就是传到lambda表达式中的圆括号中( )
}
}
Android Studio会自动帮我们生成lambda表达式:
lambda表达式支持多 个参数传递 ,例如:
public class Presenter {
public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
当监听器中的方法返回值不是void类型时,lambda表达式中的调用的方法也必须返回相同类型的值。例如,当监听Long Click事件时,你的表达式必须返回boolean类型,:
public class Presenter {
public boolean onLongClick(View view, Task task){}
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
如果表达式中需要使用谓词运算符,可以使用void作为一个符号:
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Data对象以及新增数据类型
当使用传统的数据类型来创建JavaBean对象时,修改JavaBean对象并不会自动刷新UI。但是DataBinding库为我们提供了三种不同的数据更改通知机制来使用不同的数据类型创建JavaBean,在数据改变的时候会自动刷新UI,分别是Observable objects, observable fields, and observable collections。说到底就是观察者模式的实现了,当被观察者也就是数据改变的时候,就通知观察者即UI进行刷新。
- Observable Objects
DataBinding库提供了一个BaseObservable基类,这个基类实现了observable接口,然后会在对象的所有属性上绑定一个监听器去监听属性的变化,然后发出通知。
observable接口里面包含了添加和移除监听器的方法,但是发出通知的行为则由开发者来实现。在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); } }
被@Bindable注解的属性会在编译的时候在BR class中生成一个变量条目。这个BR class自动生成在dataBinding的父目录的同级目录中,里面主要存储了我们在layout布局<r;data>中声明的变量名和被@Bindable注解 的属性名。
- ObservableFields
上面在BaseObservable子类中使用java引用类型或基本数据类型时都需要加@Bindable注解和notifyPropertyChanged方法来达到数据关联UI的效果,但是DataBinding提供了一些封装类来帮忙简化这些操作,例如ObservableField, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。从名字上可以看出,对应java中的数据类型,如boolean , byte等待。其中ObservableFields是只有一个字段的独立对象,这个字段类型时使用了T泛型,所以可以在初始化ObservableFields对象时指定数据类型。 -
private static class User { public final ObservableField<String> firstName = new ObservableField<>(); public final ObservableField<String> lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt(); }
创建了新对象后,就可以使用set和get方法获取属性值了:
user.firstName.set("Google"); int age = user.age.get();
- Observable Collections集合
DataBinding库为我们提供了几个集合包装类来帮助我们保存数据,例如:ObservableArrayMap,ObservableArrayList等。 -
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>(); user.put("firstName", "Google"); user.put("lastName", "Inc."); user.put("age", 17);
在layout布局文件中,可以通过String key来访问map中的value:
<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适合key是int的list,也就是可以通过索引来访问:
ObservableArrayList<Object> user = new ObservableArrayList<>(); user.add("Google"); user.add("Inc."); user.add(17);
在layout布局文件中,可以通过索引来访问list中的值:
<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"/>
View元素ID
当在Layout布局文件中为view元素的android:id声明一个值时,DataBinding库会自动为这个view元素生成一个public final的变量。DataBinding通过遍历View视图树,然后提取每一个带有id值的view元素。这个机制比使用findViewById方法获取view快很多。例如:
<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>
在binding类中会生成变量:
public final TextView firstName; public final TextView lastName;