Data Binding 是谷歌官方发布的一个框架,发布于2015年的Google I/O大会,顾名思义即为数据绑定。旨在减少Android开发中的大量模板代码(比如findViewById()),提高开发效率和维护效率。
Data Binding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,开发时只需要关注数据(对象)即可,无需关心View的各种繁杂操作,例如各种更新 View 属性的 setter:setText(),setVisibility(),setEnabled() 或者 setOnClickListener() 等。
Data Binding 通常与 ViewModel 、LiveData 组合使用来进行响应式UI设计。即:当底层数据被更新时,UI也会相应的自动更新。
ViewModel + LiveData + Data Binding = 响应式UI
Data Binding 也是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常。
综上,Data Binding具有以下优点:
- 减少样板代码
- 降低布局和逻辑的耦合性
- 实现数据(对象)与View的双向绑定
- 防止内存泄漏
- 避免空指针异常
一、Data Binding 基本用法
1. 启用 Data Binding
在 app 的 build.gradle 文件下加入以下代码,同步后就能引入对 Data Binding 的支持。
android {
...
dataBinding { //也可以添加在defaultConfig中
enabled true
}
}
2. 将布局文件转换成Data Binding Layout
打开布局文件,选中根布局的 ViewGroup,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则。
多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型 。
3. 创建所需要的 JavaBean,并在 data 标签中声明
<data>
<variable
name="userInfo"
type="com.leavesc.databinding_demo.model.User" />
</data>
如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用。
<data>
<import type="com.leavesc.databinding_demo.model.User"/>
<variable
name="userInfo"
type="User"/>
</data>
4. 设置对象的属性值,使 TextView 显示相应的文本
<?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>
<import type="com.leavesc.databinding_demo.model.User" />
<variable
name="userInfo"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical"
tools:context="com.leavesc.databinding_demo.Main2Activity">
<TextView
android:id="@+id/tv_userName"
···
android:text="@{userInfo.name}" />
<TextView
···
android:text="@{userInfo.password}" />
</LinearLayout>
</layout>
5. 在 Activity 中指定对应的布局文件,并将 Data Binding 绑定的对象注入
在 Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法。
private User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain2Binding activityMain2Binding = DataBindingUtil.setContentView(this, R.layout.activity_main2);
user = new User("leavesC", "123456");
activityMain2Binding.setUserInfo(user);
}
以上就是 Data Binding 的基本用法,更多用法可参见 Android DataBinding 从入门到进阶
二、Data Binding 实现双向绑定
使用 Data Binding 实现双向绑定的前提是需要结合 ViewModel 、LiveData 一起使用,即实现响应式UI,从而当数据更新时,UI也能够相对应的自动更新。
1. 启用 Data Binding
2. 编写符合业务逻辑的 ViewModel
public class MyViewModel2 extends ViewModel {
private MutableLiveData<Integer> number;
public MutableLiveData<Integer> getNumber() {
if (number == null) {
number = new MutableLiveData<>();
number.setValue(0);
}
return number;
}
public void add() {
number.setValue(number.getValue() + 1);
}
}
3. 将布局文件转换成Data Binding Layout,并绑定 ViewModel
<?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="data"
type="software.baby.learnjetpack.MyViewModel2" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Main3Activity">
<TextView
android:id="@+id/textView"
android:layout_width="15dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@{String.valueOf(data.number)}"
android:textSize="26sp"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="296dp"
android:onClick="@{()->data.add()}"
android:text="@string/btn_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
4. 完善控制逻辑代码,实现双向绑定
public class Main3Activity extends AppCompatActivity {
MyViewModel2 myViewModel;
ActivityMain3Binding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main3);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main3);
myViewModel = ViewModelProviders.of(this).get(MyViewModel2.class);
binding.setData(myViewModel);
binding.setLifecycleOwner(this); //不能省略,指定LifecycleOwner
}
}
注意:如果我们在步骤3中,不去绑定对应的ViewModel,那么这时候数据的绑定是单向的。
<?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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Main3Activity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="240dp"
android:layout_marginEnd="8dp"
android:text="@string/text_result"
android:textSize="26sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="296dp"
android:text="@string/btn_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
public class Main3Activity extends AppCompatActivity {
MyViewModel2 myViewModel;
ActivityMain3Binding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main3);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main3);
myViewModel = ViewModelProviders.of(this).get(MyViewModel2.class);
myViewModel.getNumber().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
binding.textView.setText(String.valueOf(integer));
}
});
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
myViewModel.add();
}
});
}
}