简介
DataBinding是一个实现数据和UI绑定的框架,是实现MVVM模式的工具。在使用DataBinding之前我们不可避免地要编写大量的毫无营养的代码,如 findViewById()、 setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可 以通过声明式布局以精简的代码来绑定应用程序逻辑。
缺点:
- 灵活性较低
- Model发生变化时,ViewDatabinding采用异步更新数据,对于现实大量数据的ListView,会有一定延迟,效率太低
- 自动生成大量代码和属性字段:ViewDataBinding 实现类 DataBinderMapper 等
- ViewModel与View一一对应
使用步骤
-
配置环境
在build.gradle文件中设置
javadataBinding{ enabled=true } -
数据绑定布局文件
先写出一个Model实体类
javapublic class User { private String username; private int age; public User(String username,int age){ this.username=username; this.age=age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } }数据绑定的布局文件和常用的不同,从布局根标记开始,后面依次是数据元素和视图根元素
控件要使用绑定的数据通过
@{}(单向绑定)或@={}(双向绑定)实现xml<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" > <data> <!-- 数据对象,name 是变量名,type 是类的全路径 --> <variable name="user" type="com.example.mvvmtext.model.User" /> <variable name="handler" type="com.example.mvvmtext.MainActivity.Handler" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center"> <!-- 这里的 user.name 调用的实际上是 User 类中的 getName() 方法,如果没有对应的 get 方法,就会报错 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:id="@+id/tv_show"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:textSize="20dp" android:text="@{user.username}"/> <!-- 我们知道 TextView 的内容必须是 String 类型的,这里传入 int 会报错 java.lang 包下的类不需要导入 --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:textSize="20dp" android:text="@{String.valueOf(user.age)}"/> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{()->handler.onClick()}"/> </LinearLayout> </layout> -
获取binding对象
java/* 使用DataBinging布局后这里需要DataBindingUtil来完成setContentView,返回值为布局的ViewBinding对象,这个类由DataBinding框架自动生成 */ ActivityDataBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_data);在Fragment中
javapublic class MainFragment extends Fragment { @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { //DataBinding 布局是可以使用下面两种方式,inflate 出来的。 // FragmentMainBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false); FragmentMainBinding binding = FragmentMainBinding.inflate(inflater, container, false); return binding.getRoot(); } }还要在Activity/Fragment中给实体类赋值
javapublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); ActivityDataBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_data); User user=new User("mm",28); //如果不传 user 或不给他某个参数赋值呢? //答:DataBinding不惧怕空指针异常,若表达式结果为null,则根据其结果的值类型显示不同,比如引用类型显示null,int类型显示0,string类型显示空 binding.setUser(user); binding.setHandler(new Handler()); } public class Handler{ public void onClick(){ Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show(); } } } -
表达式的使用
关键字
可以在
@{}中使用以下运算符和关键字- 运算类 + - / * % && || & | ^ ! ~ == > < >= <= () >> >>> <<
- 字符串连接 + (注意字符串要用" "括起来)
- instanceof
- 文字 - 字符,字符串,数字, null
- 强转 cast
- 方法调用
- res 资源访问
- 数组访问 []
- 三目运算 ?:
- 合并运算 ?? ------选择左边的是 null 吗,不是选左边,如果是选右边
不支持的操作
- this
- super
- new
- 显示泛型调用
-
事件绑定
-
方法引用:事件可以直接绑定到处理程序方法,表达式在编译时处理,如果方法不存在或其签名不正确,会收到编译时错误
在方法引用中,方法的参数必须与事件侦听器的参数匹配。
编写一个事件
javapublic class Handler{ public void onClick(){ Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show(); } }在layout文件中注册,并绑定控件
xml<data> <variable name="user" type="com.example.mvvmtext.model.User" /> <variable name="handler" type="com.example.mvvmtext.MainActivity.Handler" /> </data> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{handler::onClick}"/>最后在java代码中将事件绑定到布局
javabinding.setHandler(new Handler()); -
监听器绑定:发生事件时运行的绑定表达式,与方法引用类似,但允许运行任意数据绑定表达式。
在监听器绑定中,返回值必须与监听器的期望返回值相匹配(除非它为void)
编写一个事件
javapublic class Handler{ public void onClick(){ Toast.makeText(MainActivity.this,"登录",Toast.LENGTH_SHORT).show(); } }在layout文件中注册,并绑定控件
xml<data> <variable name="user" type="com.example.mvvmtext.model.User" /> <variable name="handler" type="com.example.mvvmtext.MainActivity.Handler" /> </data> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{()->handler.onClick()}"/>最后在java代码中将事件绑定到布局
javabinding.setHandler(new Handler());如果想在表达式中使用参数,可以采用以下方式
javapublic void onClick1(View view,User user){ view.setBackgroundColor(Color.BLUE); }xml<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{(v)->handler.onClick1(v,user)}"/>如果正在监听的事件返回值不为void,则表达式也必须返回相同类型的值,如监听长按事件,返回值应为boolean
javapublic boolean onLongClick(View view, Task task){ ... }xmlandroid:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
-
布局属性
-
import用法与别名
data节点支持import用法,并且需要具体的类名
xml<import type="com.example.mvvmtext.model.User"/> <variable name="user" type="User" />如果import引用了两个相同的类名,我们可以用别名来区分它们
xml<import type="com.example.mvvmtext.model.User"/> <import type="com.example.mvvmtext.User" alias="Muser"/> <variable name="user" type="User" /> <variable name="muser" type="Muser" /> -
变量定义
我们也可以在XML中定义一些基本数据类型和String类型,java.lang.*中的类会自动导入,无需使用import
xml<variable name="name" type="String" /> <variable name="age" type="int" /> <variable name="man" type="boolean" />除此之外,我们还可以定义List、Map等集合变量
xml<import type="java.util.ArrayList"/> <import type="java.util.Map"/> <variable name="map" type="Map<String,String>" /> <variable name="list" type="ArrayList<String" /> <variable name="arrays" type="String[]" /> -
自定义Binding类名
可以通过修改布局文件中data节点的class属性,改变Binding类的类名和生成方式
xml//全类名方式,此时Binding类位于com.model包中,名称为ActivityFirst <data class="com.model.ActivityFirst"> //仅改变类名,不改变生成位置 <data class="ActivityFirst"> //改变位置到项目的包的根目录下 <data class=".ActivityFirst"> -
静态方法的调用
在布局文件中也可以直接调用静态方法
javapublic class Util{ public static String getName(User user){ return user.getUsername(); } }xml<import type="com.example.mvvmtext.MainActivity.Util"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dp" android:text="@{Util.getName(user)}"/> -
Converter
转换器,把数据格式转为需要的格式
xml<variable name="time" type="java.util.Date" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{time}"/>text属性需要String类型的值,这里给的是Date类型的值,需要写一个Converter来进行类型转换
javapublic class Util{ @BindingConversion public static String converDate(Date date){ SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); return sdf.format(date); } }coverDate在哪个类中不重要,重要的是要有@BindingConversion注解
动态更新和双向绑定
实现Model实体类的内容发生变化,UI动态更新
DataBinding提供了3种动态更新机制:ObservableField(字段)、Observable(类)、Observable(容器类)
结合动态更新机制,还可以实现双向绑定:当View发生改变时,ViewModel也会通知Model进行数据更新
(1)使用Observable
通过继承BaseObservable实现动态更新
java
public class ObUser extends BaseObservable {
private String name;
private String level;
public ObUser(String name,String level){
this.name=name;
this.level=level;
}
//用@Bindable注解标记的getter会在BR中生成一个相应的字段
@Bindable
public String getName() {
return name;
}
@Bindable
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
//通知系统BR.level字段发生了改变,并更新UI
notifyPropertyChanged(BR.level);
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
}
java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
ActivityMainBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
ObUser obUser=new ObUser("mm","A");
binding.setObuser(obUser);
binding.btUpdataObuser.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
obUser.setName("太子");
}
});
}
}
xml
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="obuser"
type="com.example.mvvmthree.model.ObUser" />
</data>
<LinearLayout
android:orientation="vertical"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{obuser.name}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{obuser.level}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bt_updata_obuser"/>
</LinearLayout>
</layout>
(2)使用ObservableField
除了集成BaseObservable实现动态更新,还可以使用系统为我们提供的所有基本数据类型对应的Onservable类,如OnservableInt、OnservableFloat等,也可以使用引用数据类型和基本数据类型都支持的ObservableField
java
public class ObFUser {
public ObservableField<String> name=new ObservableField<>();
public ObservableField<String> level=new ObservableField<>();
public ObFUser(String name,String level){
this.level.set(level);
this.name.set(name);
}
public void setName(ObservableField<String> name) {
this.name = name;
}
public void setLevel(ObservableField<String> level) {
this.level = level;
}
public ObservableField<String> getLevel() {
return level;
}
public ObservableField<String> getName() {
return name;
}
}
java
ObFUser obFUser=new ObFUser("风","t");
binding.setObfuser(obFUser);
binding.btUpdataObfuser.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
obFUser.name.set("梅");
}
});
(3)使用Observable容器类
适合有多个同类型的数据需要动态更新的情况,包括ObservableArrayList和ObservableArrayMap
java
ObservableArrayList<User> list=new ObservableArrayList<>();
User user1=new User("朱","S");
User user2=new User("凤","P");
list.add(user1);
list.add(user2);
binding.setList(list);
binding.btUpdataUser.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
user1.setName("echo");
user2.setName("花");
}
});
xml
<data>
<import type="androidx.databinding.ObservableArrayList"/>
<import type="com.example.mvvmthree.model.User"/>
<variable
name="obuser"
type="com.example.mvvmthree.model.ObUser" />
<variable
name="obfuser"
type="com.example.mvvmthree.model.ObFUser" />
<variable
name="list"
type="ObservableArrayList<User>" />
</data>
(4)双向绑定
xml
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{obuser.name}"
/>
//实现双向绑定
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@={obuser.name}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bt_updata_obuser"/>
java
ActivityMainBinding binding= DataBindingUtil.setContentView(this,R.layout.activity_main);
ObUser obUser=new ObUser("mm","A");
binding.setObuser(obUser);
binding.btUpdataObuser.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
obUser.setName("太子");
}
});