资源
表达式中引用应用资源,示例如下:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
还支持格式化字符串及复数的参数传入,示例如下:
android:text="@{@string/nameFormat(firstName, lastName)}" android:text="@{@plurals/banana(bananaCount)}"
还可以把属性引用和View引用作为资源参数进行传递,示例如下:
android:text="@{@string/example_resource(user.lastName, exampleText.text)}" <!-- 当一个复数带有多个参数时,您必须传递所有参数 --> android:text="@{@plurals/orange(orangeCount, orangeCount)}"
某些资源需要显式类型求值,如下表所示:
4) 事件处理
事件属性名一般由 监听器方法名称确定,如:View.OnClickListener → onClick() → android:onClick。但存在特例,如下表:
另外,可以使用 方法引用
或 监听器绑定
来进行事件处理,两者的代码示例如下:
// 方法引用 class MyHandlers { fun onClickFriend(view: View) { ... } } <?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> // 监听器绑定 class Presenter { fun onSaveClick(task: Task){} } <?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" // lambda表达式,事件分发后会对此表达式进行求值 android:onClick="@{() -> presenter.onSaveClick(task)}" /> </LinearLayout> </layout>
不难看出区别,引用方法需要和监听器的参数一致,而监听器绑定更加灵活,可在运行时动态运行lambda表达式,参数无需一致。
上述忽略了onClick(View)的View参数,如果后面的lambda表达式有用到的话,可以定义 命名参数,示例如下:
class Presenter { fun onCompletedChanged(task: Task, completed: Boolean){} } <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
注:
- 如果监听事件的返回类型不为Void,lambda表达式也要返回相同类型的值!
- 监听器表达式这种写法功能强大,可以使代码更易阅读,但不建议写太复杂的表达式,本末倒置,反而使得布局难以阅读和维护。
变量
变量类型在编译时会进行检查,如果不同配置(如横向或纵向)有不同的布局文件,变量会合并到一起。所以这些布局文件的变量定义不要存在冲突!(如同样的变量类型不一致)
系统会根据需要生成名为 context
的特殊变量,用于绑定表达式,它的值是根视图的 getContext() 获取到的Context对象。如果另外定义了同名变量会覆盖。
包含
在include其他布局时,有时需要把变量值传递过去,可以通过 bind:变量名
进行传递,要求两个布局文件拥有同一个变量。示例如下:
<variable name="user" type="com.example.User"/> ... <include layout="@layout/name" bind:user="@{user}"/> <!-- include指向的布局 --> <variable name="user" type="com.example.User"/> ... android:text="@{user}"
注:不支持include作为merge元素的直接子元素,如这样的布局是编译不通过的:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="cn.coderpig.awayfornoise.entity.User"/> </data> <merge><!-- Doesn't work --> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </merge> </layout>
② 可观察的数据对象
DataBinding中可观察的数据对象有三种不同类型:字段、集合和对象,通过数据绑定,数据对象可在数据发生更改时通知其他对象,即监听器。
1) 可观察字段
示例如下:
class User { val firstName = ObservableField<String>() val lastName = ObservableField<String>() val age = ObservableInt() } // 访问字段值,使用set()、get() 访问器方法 user.firstName = "Google" val age = user.age
除了ObservableInt类外还有这些基本类型:
ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、ObservableParcelable
注:AS 3.1及更高版本允许使用LiveData对象替换可观察字段。
2) 可观察集合
示例如下:
ObservableArrayMap<String, Any>().apply { put("firstName", "Google") put("lastName", "Inc.") put("age", 17) } // 布局中通过字符串key找到值 <data> <import type="android.databinding.ObservableMap"/> <variable name="user" type="ObservableMap<String, Object>"/> </data> <TextView android:text="@{user.lastName}" ... /> ObservableArrayList<Any>().apply { add("Google") add("Inc.") add(17) } // 布局中通过索引访问列表 <data> <import type="android.databinding.ObservableList"/> <variable name="user" type="ObservableList<Object>"/> </data> <TextView android:text="@{user[index]}" ... />
3) 可观察对象
可以自行实现 Observable 接口,但更建议使用DataBinding提供的 BaseObservable
:
实现Observable接口,线程安全,使用 PropertyChangeRegistry 来执行 OnPropertyChangedCallback。
使用代码示例如下:
class User : BaseObservable() { @get:Bindable var firstName: String = "" set(value) { field = value notifyPropertyChanged(BR.firstName) } @get:Bindable var lastName: String = "" set(value) { field = value notifyPropertyChanged(BR.lastName) } } // 附:Java中的写法 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); } }
流程:getter设置Bindable注解
+ setter中调用notifyPropertyChanged()
。
问:上面的BR哪来的?
DataBinding会在模块包中生成名为
BR
的类,该类包含数据绑定的资源ID。在编译期,Bindable 注释会在BR类文件中生成一个条目。如果数据类的父类没办法更改,Observable接口可以使用PropertyChangeRegistry
对象实现。
③ 生成的绑定类
生成的绑定类都是继承的 ViewDataBinding
,类名基于布局名称,采用 Pascal命名法 进行转换并添加Binding 后缀,如 activity_main.xml → ActivityMainBinding。
1) 创建绑定对象
直接点开 DataBindingUtil
类,可以看到里面提供的多种绑定相关的方法:
如果可以 预知绑定类型,如ActivityMainBinding,也可以直接用ActivityMainBinding.bind()来绑定~
2) 带ID的View
DataBinding会对布局中拥有ID的每个View在绑定类中创建不可变字段。
3) 变量
DataBinding会为布局中声明的每个变量生成getter、setter方法。
4) ViewStub
占位置,惰性加载,当ViewStub被inflate或setVisible可见,它会从视图层次结构消失,如果想绑定里面的View,需要在监听 OnInflateListener,在此完成绑定
5) 即时绑定
当可变或可观察对象发生更改时,绑定会按照计划在下一帧之前发生更改。如果需要立即执行绑定,强制执行,可 executePendingBindings()
,但要注意,此方法必须运行在UI线程。