前言
Data Binding是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
布局通常是使用调用界面框架方法的代码在 Activity 中定义的。例如,以下代码调用 findViewById() 来查找 TextView 控件并将其绑定到 viewModel 变量的 userName 属性:
TextView textView = findViewById(R.id.sample_text);
textView.setText(viewModel.getUserName());
以下示例展示了如何在布局文件中使用Data Binding 将文本直接分配到TextView。这样就无需调用上述任何 Java 代码。请注意赋值表达式中 @{} 语法的使用:
<TextView
android:text="@{viewmodel.userName}" />
注意:
如果您使用Data Biding的主要目的是取代 findViewById() 调用,请考虑改用ViewBinding。
使用过ButterKnife的都知道,目前ButterKnife作者建议切换至ViewBindng使用;在许多情况下,ViewBinding可简化实现,提高性能,提供与DataBinding相同的好处。
dataBinding 的优势
双向数据绑定
数据发生改变后,dataBinding 会自动通知 UI 刷新页面,不再需要人工绑定最新数据到 View 上。UI 改变后也能同步给数据。
减少模板代码
有了 dataBinding,从此不用再写 findViewById,setOnClickListener 等枯燥生硬的代码,大大提高工作效率。从此 Butterknife 靠边站。
释放 Activity/Fragment
以前,我们在 Activity , Fragment 或 Presenter 中计算数据再绑定到 View 组件上,导致 View 层很臃肿,现在这部分工作我们可以直接在 xml 布局文件中完成。Activity , Fragment 让它更加只关注核心业务。
数据绑定空安全
在 xml 中绑定数据它是空安全的,因为 dataBinding 在数据绑定上会自动装箱和空判断,所以大大减少了数据绑定带来的 NullpointException 问题。
在使用 dataBinding 的时候,很多同学会误以为不方便调试;
现在的 databinding 在编译阶段也会有丰富的错误提示,在运行阶段,我们可以根据布局文件找到实现类,跟进去断点排查问题。
如fragment_layout_my.xml布局,在编译时会生成 FragmentLayoutMyImpl.java 实现类,我们可以搜索这种类 debug 跟进解决问题。
Data Binding使用场景:
Data Binding使用前需要先引入
在app的build.gradle中加上以下代码即可,不用引用其他的依赖
android {
...
dataBinding {
enabled = true
}
}
布局和绑定表达式
数据绑定的布局以根标记 layout 开头,后跟 data 元素和 view 根元素。如下:
注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--data 中的 user 变量描述了可在此布局中使用的属性。 -->
<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>
==系统会为每个布局文件生成一个绑定类==。
1、默认情况下,类名称基于布局文件的名称,它会转换为驼峰形式并在末尾添加 Binding 后缀。
2、以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding,==且都是ViewDataBinding的子类,所有布局对应的生成的绑定类都可以是ViewDataBinding类==
3、此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。
4、建议的绑定创建方法是在扩充布局时创建,如以下示例所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//此时可以通过DataBindingUtil来设置Activity的页面布局。
//此时会返回一个ActivityMainBinding对象。
//这个是编译时根据xml布局文件中的数据绑定自动生成的实现类。
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
//完成数据绑定
binding.setUser(user);
}
a、Activity 数据绑定 ( DataBinding ) :
1、DataBindingUtil类方法:
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
2、生成的布局绑定类的inflate()方法:
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//DataBindingUtil类方法
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//生成的布局绑定类的inflate()方法
//ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
User user = new User("Test", "User");
binding.setUser(user);
}
b、 Fragment、ListView 或 RecyclerView 适配器中使用数据绑定 ( DataBinding )
DataBindingUtil 或 生成的布局绑定类deinflate() 方法,如以下代码示例所示:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Data Binding绑定表达式:
xml里支持使用以下表达式:
算术运算符 + - / * %
字符串连接运算符 +
逻辑运算符 && ||
二元运算符 & | ^
一元运算符 + - ! ~
移位运算符 >> >>> <<
比较运算符 == > < >= <=(请注意,< 需要转义为 <)
instanceof
分组运算符 ()
字面量运算符 - 字符、字符串、数字、null
类型转换
方法调用
字段访问
数组访问 []
三元运算符 ?:
不支持以下表达式:
this
super
new
显式泛型调用
a、变量
1、生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。
2、例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。
3、如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。
<!--变量给控件赋值-->
android:text="@{user.name}"
<!--控件给变量赋值(双向绑定)-->
android:text="@={user.name}"
b、Null 合并运算符(空运算符)
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。
android:text="@{user.displayName ?? user.lastName}"
//等效于如下三目表达式
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
c、视图引用
1、表达式可以通过以下语法按 ID 引用布局中的其他视图:
2、绑定类将 ID 转换为驼峰式大小写。
3、在以下示例中,TextView 视图引用同一布局中的 EditText 视图:android:text="@{exampleText.text}"
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
d、显示隐藏控制
1、首先在 xml 的 data 节点中引用View
2、然后设置visibility
<data>
<import type="android.view.View"/>
</data>
android:visibility="@{student.boy ? View.VISIBLE : View.INVISIBLE}"
e、事件处理
方法引用:
==android:onClick="@{handlers::onClickFriend}"==
绑定表达式可将视图的点击监听器分配给MyHandlers 类的 onClickFriend() 方法,如下所示:
public class MyHandlers {
public void 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>
注意:
1、在表达式中,您可以引用符合监听器方法签名的方法。
2、当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。
3、如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。
4、表达式中的方法签名必须与监听器对象中的方法签名完全一致。
监听器绑定:
==android:onClick="@{() -> presenter.onSaveClick(task)}"==
绑定表达式可将视图的点击事件绑定打给Presenter 类的 onSaveClick(Task task) 方法,如下所示:
public class Presenter {
public void 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"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
以上,我们尚未定义传递给 onClick(View) 的 view 参数。
监听器绑定提供两个监听器参数选项:您可以忽略方法的所有参数,也可以命名所有参数。
如果您想命名参数,则可以在表达式中使用这些参数。
例如,上面的表达式可以写成如下形式:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者,如果您想在表达式中使用参数,则采用如下形式:
public class Presenter {
public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
监听长按事件,表达式应返回一个布尔值。
public class Presenter {
public boolean onLongClick(View view, Task task) { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
注意:
1、监听器绑定这些是在事件发生时进行求值的 lambda 表达式。
2、数据绑定始终会创建一个要在视图上设置的监听器。
3、事件被分派后,监听器会对 lambda 表达式进行求值。
dataBinding 可以拓展 View 属性
以前想要给 ImageView 增加几个属性,必须要写个自定义的 ImageView 在构造函数中一顿解析。
那看看使用 dataBinding 如何拓展 View 属性。
public class CustomImageView extends ImageView{
//需要使用BindingAdapter注解并标记在public static方法上。
//value中的字段随意添加和方法参数一一对应即可。
@BindingAdapter(value = {"image_url", "isCircle"})
public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle) {
view.setImageUrl(view, imageUrl, isCircle, 0);
}
//requireAll = false代表是否以下三个属性在xml中同时使用才会调用到该方法
//为false的话,只要有一个属性被使用就能调用到该方法
@BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)
public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle, int radius) {
......
}
}
//在布局文件中如下使用,便能实现图片圆角和资源Url绑定的功能
<CustomImageView
.......
app:image_url ="@{user.avatar}"
app:radius="@{50}">
</CustomImageView>
BindingAdapter :
绑定适配器,是 Jetpack DataBinding 中用来扩展布局 xml 属性行为的注解;
允许你针对布局 xml 中的一个或多个属性进行绑定行为扩展;
这个属性可以是自定义属性,也可以是原生属性。
这个扩展行为可以是简单的ViewModel属性与控件赋值绑定,也可以是关联某个控件属性的额外操作,
例如在设置属性之前进行值域检查,或类型转换,或者统一处理一些事情。
