Data Binding 布局文件 - (View)
Data binding 的布局文件与传统布局文件有一点不同。它以一个 layout 标签作为根节点,里面是 data 标签与 view 标签。view 标签的内容就是不使用 Data Binding 时的普通布局文件内容。以下是一个例子:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- 变量user, 描述了一个布局中会用到的属性 -->
<variable name="user" type="com.connorlin.databinding.model.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>
数据对象 - (Model)
假设你有一个 plain-old Java object(POJO) 的 User 对象。
public class User {
private final String mFirstName;
private final String mLastName;
private int mAge;
public User(String firstName, String lastName, int age) {
mFirstName = firstName;
mLastName = lastName;
mAge = age;
}
}
或者是 JavaBean 对象:
public class User {
private final String mFirstName;
private final String mLastName;
private int mAge;
public User(String firstName, String lastName, int age) {
mFirstName = firstName;
mLastName = lastName;
mAge = age;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
public int getAge() {
return mAge;
}
}
从 Data Binding 的角度看,这两个类是一样的。用于 TextView 的 android:text
属性的表达式@{user.firstName}
,会读取 POJO 对象的 firstName
字段以及 JavaBeans 对象的 getFirstName()
方法。
绑定数据 - (ViewModel)
在默认情况下,会基于布局文件生成一个继承于 ViewDataBinding
的 Binding 类,将它转换成帕斯卡命名并在名字后面接上Binding
。例如,布局文件叫 main_activity.xml
,所以会生成一个 MainActivityBinding
类。这个类包含了布局文件中所有的绑定关系,会根据绑定表达式给布局文件赋值。在 inflate 的时候创建 binding 的方法如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ActivityBaseBinding 类是自动生成的
ActivityBaseBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_base);
User user = new User("Connor", "Lin");
// 所有的 set 方法也是根据布局中 variable 名称生成的
binding.setUser(user);
}
事件处理
本部分源码请参考 DataBindingDemo -> EventActivity
部分。
类似于 android:onClick 可以指定 Activity 中的函数,Data Binding 也允许处理从视图中发送的事件。
有两种实现方式:
方法调用
- 监听绑定
二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。
方法调用
相较于 android:onClick ,它的优势在于表达式会在编译时处理,如果函数不存在或者函数签名不对,编译将会报错。
以下是个例子:
public class EventHandler {
private Context mContext;
public EventHandler(Context context) {
mContext = context;
}
public void onClickFriend(View view) {
Toast.makeText(mContext, "onClickFriend", Toast.LENGTH_LONG).show();
}
}
表达式如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="handler"
type="com.connorlin.databinding.handler.EventHandler"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{handler::onClickFriend}"/>
<!-- 注意:函数名和监听器对象必须对应 -->
<!-- 函数调用也可以使用 `.` , 如handler.onClickFriend , 不过已弃用 -->
</LinearLayout>
</layout>
监听绑定
监听绑定在事件发生时调用,可以使用任意表达式
此功能在 Android Gradle Plugin version 2.0 或更新版本上可用.
在方法引用中,方法的参数必须与监听器对象的参数相匹配。在监听绑定中,只要返回值与监听器对象的预期返回值相匹配即可。
以下是个例子:
public class EventHandler
{
public void onTaskClick(Task task){
task.task("task------");
}
}
public class Task {
public void task(String task){
Log.e("TAG",task);
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="eventHandler"
type="com.example.administrator.myapplication.EventHandler"></variable>
<variable
name="task"
type="com.example.administrator.myapplication.Task"></variable>
</data>
<Button
android:layout_width="wrap_content"
android:textAllCaps="false"
android:text="方法调用"
android:onClick="@{()->eventHandler.onTaskClick(task)}"
android:layout_height="wrap_content"/>
</layout>
当一个回调函数在表达式中使用时,数据绑定会自动为事件创建必要的监听器并注册监听。
关于参数
- 参数有两种选择:要么不写,要么就要写全。
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> handler.onTaskClick(task)}" />
或
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view) -> handler.onTaskClick(task)}"/>
- lambda 表达式可添加一个或多个参数,同时参数可任意命名
public class EventHandler {
public void onTaskClickWithParams(View view, Task task) {
task.run();
}
}
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{(theview) -> handler.onTaskClickWithParams(theview, task)}" />
或者
public class EventHandler {
public void onCompletedChanged(Task task, boolean completed) {
if(completed) {
task.run();
}
}
}
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> handler.onCompletedChanged(task, isChecked)}" />
-
表达式结果有默认值 null、0、false等等
-
表达式中可以使用void
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}" />
关于表达式
-
复杂的表达式会使布局难以阅读和维护,这种情况我们最好将业务逻辑写到回调函数中
-
也有一些特殊的点击事件 我们需要使用不同于 android:onClick 的属性来避免冲突。
下面是一些用来避免冲突的属性:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
布局详情
本部分源码请参考 DataBindingDemo -> CombineActivity
部分
导入(Imports)
- data 标签内可以有多个 import 标签。你可以在布局文件中像使用 Java 一样导入引用
<data>
<import type="android.view.View"/>
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
- 当类名发生冲突时,可以使用 alias
<import type="android.view.View"/>
<import type="com.connorlin.databinding.ui.View" alias="AliasView"/>
- 导入的类型也可以用于变量的类型引用和表达式中
<data>
<import type="com.connorlin.databinding.model.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
注意:Android Studio 还没有对导入提供自动补全的支持。你的应用还是可以被正常编译,要解决这个问题,你可以在变量定义中使用完整的包名。
- 导入也可以用于在表达式中使用静态方法
public class MyStringUtils {
public static String capitalize(final String word) {
if (word.length() > 1) {
return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
}
return word;
}
}
<data>
<import type="com.connorlin.databinding.utils.MyStringUtils"/>
<variable name="user" type="com.connorlin.databinding.model.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- java.lang.* 包中的类会被自动导入,可以直接使用,例如, 要定义一个 String 类型的变量
<variable name="test" type="String" />
变量 Variables
- data 标签中可以有任意数量的 variable 标签。每个 variable 标签描述了会在 binding 表达式中使用的属性。
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.connorlin.databinding.model.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
- 可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法。
<TextView
android:id="@+id/first_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={user.firstName}" />
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{firstName.getVisibility() == View.GONE ? View.GONE : View.VISIBLE}" />
<!-- 这里TextView直接引用第一次TextView,firstName为id 的驼峰命名 -->
- binding 类会生成一个命名为 context 的特殊变量(其实就是 rootView 的 getContext() ) 的返回值),这个变量可用于表达式中。 如果有名为 context 的变量存在,那么生成的这个 context 特殊变量将被覆盖。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{handler.loadString(context)}"/>
public String loadString(Context context) {
// 使用生成的context变量
return context.getResources().getString(R.string.string_from_context);
}
参考链接:
http://connorlin.github.io/2016/07/02/Android-Data-Binding-%E7%B3%BB%E5%88%97-%E4%B8%80-%E8%AF%A6%E7%BB%86%E4%BB%8B%E7%BB%8D%E4%B8%8E%E4%BD%BF%E7%94%A8/
https://github.com/LyndonChin/MasteringAndroidDataBinding