引入DataBinding
要在当前module的build.gradle文件中添加如下代码
android{
...
dataBinding {
enabled = true
}
...
}
快速使用
第一步 :创建对象
一个普通的java对象即可
public class User {
public String name;
public String phone;
}
第二步 :修改布局
- 规范布局:在布局文件最外层添加一个 layout 的根标签
- 引入数据:在 layout 标签下,添加 data 标签
- 声明对象:在 data 标签中添加 variable 的标签,其中 name 表示对象名,type 表示类名(包含包名)
- 关联属性:通过表达式 “@{}” 获取对象的属性,并将他们绑定到控件中
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="user" type="cn.com.ursus.User"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"/>
</RelativeLayout>
</layout>
注意事项:
当 User 中的有公有属性 name 时,@{user.name} 相当于 user.name
当 User 中的无公有属性 name 时,@{user.name} 相当于 user.getName()给 android.text 绑定属性的时候,注意转成字符串,如果是整型,会被当成资源id处理,可以参考下面的代码(字符串用双引号 " " 原先外面那层双引号转成单引号' ')
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{student.age + ""}'/>
第三步 :绑定对象
- 在 Activity 的 onCreate 方法中,用 DataBindingUtil.setContentView 来替换原来的 setContentView ,得到一个名为 ActivityMainBinding 对象。( ActivityMainBinding 对象是根据布
局文件自动生成的,名称来自于布局文件的名称配合上驼峰规则。) - 通过刚才生成的 ActivityMainBinding 将和布局绑定的对象设置进去
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User();
user.name = "luffy";
user.phone = "130****5678";
binding.setUser(user);
}
这样一个最基本的数据绑定就完成了。
Observable
绑定完之后,肯定希望的是 User 对象中的属性值改变之后,绑定的控件也跟着自动刷新,然而并没有,于是乎,需要对 User 对象进行如下改造。
public class User extends BaseObservable{
private String name;
private String phone;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
notifyPropertyChanged(BR.phone);
}
}
注解 @Bindable 修饰 getName 方法可在 BR 类中自动生成一个对应属性 name 的整型常量 BR.name。 使用 notifyPropertyChanged 方法即可刷新绑定改属性的控件。至于 BR 是什么,可以类比为 Android 中的 R
如果一个类中只有个别属性别绑定到ui,需要即使刷新,而整个类又不想继承
BaseObservable ,可以使用 ObservableField , 具体可以参考下面的代码
public class Student {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableField<String> grade = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
...
final Student student = new Student();
student.name.set("Ace");
student.grade.set("grade2");
student.age.set(1);
binding.setStudent(student);
表达式和事件
前面提过为了避免 android:text 将整型识别为资源文件,需要将整型转成字符串。
android:text='@{student.age + ""}'
由此可见在 @{} 中进行一些简单的表达式操作。
三目运算符 ?:
android:text="@{ user.phone != null ? user.phone : @string/no_phone}"
Null Coalescing Operator ??
这个不是 java 代码的语法,Databinding 自定义的,类似于三目运算符特殊情况的一种简易写法
android:text="@{ user.phone ?? @string/no_phone}"
这和上面那种写法是等价的
使用静态属性和静态方法
上面代三目运算符的例子,如果我们要控制某个控件的显示与否可以这么写
android:visibility="@{ user.phone != null ? View.GONE : View.VISIBLE}"
这里不可以使用 gone 、 visible , 必须使用 View.GONE 和 View.VISIBLE
可是这个 View 是哪里来的? 我们可以在 data 标签中 import 进来
<data>
<import type="android.view.View"/>
<import type="android.text.TextUtils"/>
</data>
import 进来之后,我们就可以也只能使用其中的静态属性和静态方法
android:visibility="@{TextUtils.isEmpty(user.phone) ? View.GONE : View.VISIBLE}
注意:
如果两个 import 进来的两个类,类名相同,我们可以给他们设置别名
<import alias="MainActivityPresenter"
type="cn.com.ursus.PermissionUtils"/>
<import alias="ActivityPresenter"
type="cn.com.ursus.presenter.PermissionUtils"/>
资源文件
上面的几个例子中在 @{} 中用到了 @string 资源文件,那么可以使用带占位符的 @string 吗?当然可以
<string name="welcome_name">Welcome,%s</string>
...
android:text="@{@string/welcome_name(user.name)}"
当然除了 @string ,@dimen、@color 等资源文件也肯定是支持的
事件
我们可以在 @{} 中可以用表达式来响应事件,比如最常用的 onClick,我们可以之间在之前的 User 类中编写相应的方法来响应,不过此处我重新创建一个类专门处理响应事件。
class Presenter{
public void clickUserName(View v){...}
public void userNameChanged(CharSequence s, int start, int before, int count) {...}
}
...
android:onClick="@{presenter.clickUserName}"
android:onTextChanged="@{presenter.userNameChanged}"
在 Databinding 中只需要响应 onTextChanged ,无需理会 beforeTextChanged afterTextChanged ,然而如果在代码中实现 onTextChanged 我们一般都会采用如下的方式,就会显得有些臃肿。
tvName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
我们自定义的响应方法必须和常规监听方法保持一致的参数吗?答案是否定的,我们可以自己定义响应方法的参数,不过表达式和先前略有不同,类似lambda
public void clickUserName(View v,String username){
...
}
...
android:onClick="@{(v)->presenter.clickUserName(v,user.name)}
还有一些表达式就不一一列举了,下面是从官方Data Binding Guide上复制下来的目前 @{} 支持的表达式
- Mathematical
+ - / * %
- String concatenation
+
- Logical
&& ||
- Binary
& | ^
- Unary
+ - ! ~
- Shift
>> >>> <<
- Comparison
== > < >= <=
- instanceof
- Grouping
()
- Literals - character, String, numeric,
null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator
?:
- Null Coalescing Operator
??
自定义属性
自动属性
我们现在自定义了一个控件,其中有一个如下的 setPhoneNumber 方法
public class MyTextView extends TextView {
...
public void setPhoneNumber(String phone) {
if (!isPhoneNumber(phone)) {
throw new IllegalArgumentException("手机号格式不正确");
}
String show = phone.substring(0, phone.length() - (phone.substring(3)).length())
+ "****"
+ phone.substring(7);
setText(show);
}
...
}
神奇的一幕发生了 ,我们可以直接在布局文件中使用 phoneNumber 的布局属性,虽然 MyTextView 中并没有该属性
<cn.com.ursus.view.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:phoneNumber="@{user.phone}"/>
自定义属性
现在有个需求,项目用的 Picasso 图片框架,我们需要在ImageView中自定义一个布局属性,使得我们可以给该属性设置一个网络url时,自动使用 Picasso 图片框架来加载网络图片,该如何做?我们只需写一个静态方法,给他打上一个 @BindingAdapter
@BindingAdapter({"image_url"})
public static void setImageUrl(ImageView view, String url){
Picasso.with(MainApplication.getContext())
.load(url)
.placeholder(R.mipmap.ic_launcher)
.into(view);
}
然后就可以在布局中使用该属性了
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:image_url="@{user.icon}"/>
那么这个 setImageUrl 方法该放在哪个类里呢?需要将那个类导入布局么?
其实 setImageUrl 方法可以放在任意类里面,而且不需要导入布局中,不过同一个控件的自定义属性一般放在一起管理。