(二)Android Jetpack 组件之数据绑定库 (DataBinding)
文章目录
1、概述
数据绑定库(DataBinding)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
主要特性简单来说:
- I、取代findVewById这种写法,Butterknife(黄油刀)这个库也可以不用了;
- II、可以直接在布局文件xml中绑定数据(定义变量、调用变量、控件值可直接赋值变量)(基本数据类型、Map、List、实体)变量;
- III、数据绑定库支持双向数据绑定。此类绑定使用的表示法支持以下操作:接收对属性的数据更改,同时监听用户对此属性的更新。
使用数据绑定库之前我们需要做2件事:
- I、在app的build.gradle文件中开启配置dataBinding;
- II、创建data binding layout布局。
这个支持库在Android Studio3.6.1版本里面已经预制了,所以我们只需要在app的build.gradle文件中添加一行配置即可:
dataBinding {
enabled = true
}
为什么叫数据绑定?根据案例来具体感受吧。
2、如何创建data binding layout 的布局?
为什么data binding layout布局会不同,因为我们要直接在布局文件xml中进行数据绑定操作,所以改变布局xml是必然的。
来看一张 常规布局文件xml vs data binding layout 布局xml,如下所示:
从此有了这个data binding layout布局文件了。接下来我们需要在这个布局文件中添加一些变量及变量赋值等操作。
举个例子:
<?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="userName"
type="java.lang.String" />
<!--整型包装类-->
<variable
name="age"
type="java.lang.Integer" />
<!--整型-->
<variable
name="sex"
type="int" />
<!--点击事件-->
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".NormalActivity">
<!-- 调用 data 标签里面的变量 赋值-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userName}" />
<!-- 还可以进行类型转换 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(age)}" />
<!-- 还可以支持三元运算 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(sex==1?'男':'女')}" />
<!-- 还可以绑定点击事件 -->
<Button
android:id="@+id/na_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{onClickListener}"
android:text="点我" />
</LinearLayout>
</layout>
非常直观,核心就是< data >标签类的声明变量,可以在< data >标签外直接使用,还可以支持Map、List等等。布局文件准备好了,我们接下来应该关注一下Activity里面该如何去使用变量或点击事件。
与上面布局文件对应的Activity:
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import com.hb.mvvm.databinding.ActivityNormalBinding;
public class NormalActivity extends AppCompatActivity implements View.OnClickListener {
ActivityNormalBinding dataBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_normal);
//DataBindingUtil.setContentView 接管了上面这句话,并得到了一个自动注入的 ActivityNormalBinding对象
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_normal);
initData();
}
private void initData() {
dataBinding.setUserName("小明");
dataBinding.setAge(20);
dataBinding.setSex(1);
//先设置点击事件监听
dataBinding.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.na_btn:
//响应对应按钮触发的动作
Toast.makeText(this,"触发点击事件",Toast.LENGTH_SHORT).show();
break;
}
}
}
在例子中的onCreate方法中,可以看到DataBindingUtil.setContentView()这个方法接管了原来的setContentView(),返回了一个ActivityNormalBinding这个对象的实例,通过ActivityNormalBinding这个对象的实例我们就可以给布局文件xml中声明的变量赋值或事件了。
ActivityNormalBinding哪来的?
回答:自动生成的,为什么直接输入的时候报错提示找不到这个类呢?要想自动生成它必须满足,布局文件xml是data binding layout的布局,然后在Activity中通过Alt+Enter进行导入,有时候也可能是编译器的问题,点下[sync同步按钮]可解决。
ActivityNormalBinding类的命名规律:
R.layout.activity_normal + Binding =ActivityNormalBinding 就是这样。
如: R.layout.activity_main + Binding =ActivityMainBinding。
输出结果:
3、单向数据绑定与双向数据绑定
3.1、单向数据绑定
注意:单向绑定变量我们使用: @{变量名}。
单向的意思就是在Activity中设置变量的值,会直接反应在xml的控件上。
单向数据绑定的时候我们不得不关注下这三个类,ObservableField、BaseObservable、ObservableCollection,直观感觉是观察者模式的类。
Observable开头的类有什么用处:
BaseObservable:需要结合@Bindable注解使用进行观察数据变化,属性所在类需要继承它,通知属性值变化需要手动调用 notifyPropertyChanged(BR.属性名)或notify()。
ObservableField:声明变量会自动创建get set方法,同时自动为属性添加数据变化监听,可以添加泛型,同时DataBinding库已经封装了基本数据类型和List、Map等,ObservableInt、ObservableDouble。
ObservableCollection:集合,这里和我们常用的 List 、Map一样。只不过这里的ObservableList、ObservableMap是封装好的。当我们改变集合里的数据时。xml也会改变。唯一要注意的是,在xml里引用这些集合的时候<类型>,这些符号,会影响xml格式所以要转义。用< 代表<;用>代表>(这些转义符,同样支持Mark Down);
说到底为什么会有这三个类型呢?
答:个人理解是首先以前写一个对象类需要自己手动写get set方法,同时数据变化的时候很少去关注那些属性值变化了,Observable打头说明实现了观察者模式,当数据变化我们可以及时感知到,提醒xml数据更新。同时为了更多的支持不同类型,8大基本数据类型、引用类型、list、map都进行了Observable封装,可以实时的观察这些数据类型值的变化。
3.2、双向数据绑定
注意:双向绑定变量我们使用 :@={变量名},比单向绑定多了一个=号。
双向的意思就是在Activity中设置变量的值,不仅会直接反应在xml的控件上;同时在xml的EditText等输入型控件上输入值可以直接传递给Activity中的变量并改变变量的值。
如:你在Activity设置了变量a的值为’test’,你在EditText的android:text属性上双向绑定了变量a,则这个EditText上会显示出这个“test”,不需关闭程序,这时候你继续在EditText上删除‘test’时,activity里面的变量a的值也会变成空,当你再次继续输入“hellow”,这时在Activity里面的变量a的值也会变成“hellow”。
3.3、具体案例
activity_normal.xml
<?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="name"
type="java.lang.String" />
<!--整型包装类-->
<variable
name="age"
type="java.lang.Integer" />
<!--整型-->
<variable
name="sex"
type="int" />
<!--点击事件-->
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
<!-- 属性数据变化监听 双向绑定-->
<variable
name="loginBean"
type="com.hb.mvvm.entity.LoginBean" />
<!-- 属性数据变化监听 双向绑定-->
<variable
name="boy"
type="com.hb.mvvm.entity.Boy" />
<!-- Map类型用法,使用带观察者的列表对象ObservableMap 导包-->
<import
alias="Map"
type="androidx.databinding.ObservableMap" />
<!-- Map类型用法 声明变量 , 因在xml中不能直接使用<和>符号,所以:<代表<,>代表>-->
<variable
name="map"
type="Map<String,Object>" />
<!-- List类型用法,使用带观察者的列表对象ObservableList 导包-->
<import
alias="List"
type="androidx.databinding.ObservableList" />
<!-- List类型用法 声明变量,因在xml中不能直接使用<和>符号,所以:<代表<,>代表>-->
<variable
name="list"
type="List<String>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".NormalActivity">
<!-- 调用 data 标签里面的变量 赋值-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{name}" />
<!-- 还可以进行类型转换 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(age)}" />
<!-- 还可以支持三元运算 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(sex==1?'男':'女')}" />
<!-- 还可以绑定点击事件 -->
<Button
android:id="@+id/na_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{onClickListener}"
android:text="点我" />
<!-- 通过 ObservableField 观察值变化-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{boy.name}" />
<!--双向数据绑定 通过 ObservableField 观察值变化-->
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={boy.name}" />
<!--双向数据绑定,改变EditText的值也会改变 LoginBean 属性 userName的值-->
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:hint="请输入账号"
android:text="@={loginBean.userName}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword"
android:text="@={loginBean.password}" />
<Button
android:id="@+id/na_btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{onClickListener}"
android:text="登录" />
<!-- map变量使用-->
<TextView
android:layout_width="wrap_content"
android:text='@{map["name"]}'
android:layout_height="wrap_content" />
<!-- list变量使用-->
<TextView
android:layout_width="wrap_content"
android:text='@{list[0]}'
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:text='@{list.get(1)}'
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
NormalActivity.class
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.Observable;
import androidx.databinding.ObservableArrayList;
import androidx.databinding.ObservableArrayMap;
import androidx.databinding.ObservableList;
import androidx.databinding.ObservableMap;
import com.hb.mvvm.databinding.ActivityNormalBinding;
import com.hb.mvvm.entity.Boy;
import com.hb.mvvm.entity.LoginBean;
public class NormalActivity extends AppCompatActivity implements View.OnClickListener {
ActivityNormalBinding dataBinding;
LoginBean loginBean;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_normal);
//DataBindingUtil.setContentView 接管了上面这句话,并得到了一个自动注入的 ActivityNormalBinding对象
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_normal);
initData();
}
Boy boy;
private void initData() {
dataBinding.setName("小明");
dataBinding.setAge(20);
dataBinding.setSex(1);
//先设置点击事件监听
dataBinding.setOnClickListener(this);
//传递对象双向绑定数据
loginBean = new LoginBean();
dataBinding.setLoginBean(loginBean);
boy = new Boy();
dataBinding.setBoy(boy);
//通过ObservableField通用类、ObservableInt、ObservableDouble等基本数据类型,监听属性变化
boy.name.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
//sender: The Observable that is changing.
//propertyId: The BR identifier of the property that has changed. The getter for this property should be annotated with Bindable.
Log.d(getPackageName(), "监听到属性发生变化:" + boy.name + " ," + propertyId);
}
});
//继承BaseObservable 观察属性变化
loginBean.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == BR.userName) {
Log.d(getPackageName(), "监听到属性发生变化:" + loginBean.getUserName());
} else if (propertyId == BR.password) {
Log.d(getPackageName(), "监听到属性发生变化:" + loginBean.getPassword());
}
}
});
//Map使用
ObservableMap<String, Object> map = new ObservableArrayMap<>();
map.put("name", "james");
dataBinding.setMap(map);
//List使用
ObservableList<String> mList = new ObservableArrayList<>();
mList.add("Lucy");
mList.add("LilySi");
dataBinding.setList(mList);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.na_btn:
//响应对应按钮触发的动作
Toast.makeText(this, "触发点击事件", Toast.LENGTH_SHORT).show();
break;
case R.id.na_btn_login:
//获取xml绑定的数据
String userName = loginBean.getUserName();
String password = loginBean.getPassword();
Log.d(getPackageName(), userName + "," + password);
//响应对应按钮触发的动作
Toast.makeText(this, userName + "正在登录。。。", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
Boy.class
import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;
/**
* Created by Administrator
* on 2020/3/25
*/
public class Boy {
// 自动创建get set 方法
public final ObservableField<String> name = new ObservableField<>();
// 自动创建get set 方法
public final ObservableInt age = new ObservableInt();
// 自动创建get set 方法
public final ObservableInt sex = new ObservableInt();
public Boy() {
}
public Boy(String name, int age, int sex) {
this.name.set(name);
this.age.set(age);
this.sex.set(sex);
}
}
LoginBean.class
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.hb.mvvm.BR;
/** 单向绑定数据并监听set方法
* Created by Administrator
* on 2020/3/26
*/
public class LoginBean extends BaseObservable {
private String userName;
private String password;
public LoginBean() {
}
public LoginBean(String userName, String password) {
this.userName = userName;
this.password = password;
}
//aaa@qq.com标志,然后会生成BR.name
@Bindable
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
//只刷新userName字段
notifyPropertyChanged(BR.userName);
}
@Bindable
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
//刷新password所有字段
notifyPropertyChanged(BR.password);
}
}
输出结果:
4、在RecyclerView.Adapter的应用
获取根节点使用iewDataBinding.getRoot() 方法,载入布局使用DataBindingUtil.inflate。同时对应的xml都需要使用data binding layout 布局。
举个例子:
User.class
package com.hb.mvvm.entity;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class User extends BaseObservable {
private String name;
private int age;
private int sex;
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//只刷name字段
notifyPropertyChanged(com.hb.mvvm.BR.name);
}
@Bindable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
//只刷name字段
notifyPropertyChanged(com.hb.mvvm.BR.age);
}
@Bindable
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
//只刷name字段
notifyPropertyChanged(com.hb.mvvm.BR.sex);
}
public User(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import
alias="User"
type="com.hb.mvvm.entity.User" />
<variable
name="user"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{user.name}" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:text="@{String.valueOf(user.age)}" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:text="@{String.valueOf(user.sex==1?'男':'女')}" />
</LinearLayout>
</layout>
ListAdapter.class
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.RecyclerView;
import com.hb.mvvm.R;
import com.hb.mvvm.databinding.ItemListBinding;
import com.hb.mvvm.entity.User;
import java.util.List;
/**
* Created by Administrator
* on 2020/3/25
*/
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.MyViewHolder> {
private List<User> mList;
public ListAdapter(List<User> mList) {
this.mList = mList;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemListBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false);
return new MyViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
ItemListBinding binding = (ItemListBinding) holder.viewDataBinding;
binding.setUser(mList.get(position));
}
@Override
public int getItemCount() {
return null != mList ? mList.size() : 0;
}
public class MyViewHolder extends RecyclerView.ViewHolder {
ViewDataBinding viewDataBinding;
public MyViewHolder(ViewDataBinding viewDataBinding) {
super(viewDataBinding.getRoot());
this.viewDataBinding = viewDataBinding;
}
}
}
5、总结
很显然onCreate方法中 setContentView(R.layout.activity_normal);这个语句没有了;
不再需要findViewById()方法了;
在data binding layout中声明变量使用 < variable > 标签,可以声明各种类型,包括实体变量;
在data binding layout中使用变量直接通过 @{变量名} 来使用,还可以进行拆箱、类型转换,三元运算等等操作;
在data binding layout中双向绑定变量通过 @={变量名} 来使用,