什么是 DataBinding
DataBinding 是一个让你可以把布局和数据绑定起来的库,我们可以通过 官方文档 来学习如何使用 DataBinding。
我们一般通过这种方式给 UI 设置数据:
java
TextView textView = findViewById(R.id.sample_text);
if(textView != null && user != null){
textView.setText(user.getName());
}
使用 DataBinding 后,上面这些代码都不需要写了,直接通过配置布局文件就可以实现上面代码的功能,注意下面的@{}语法。
xml
<TextView
android:text="@{user.name}" />
使用 DataBinding 的好处:
-
- findViewById、setText 这些代码都不用写了,代码更容易维护。
-
- textView 的空检查不需要了,因为绑定的逻辑写在 textView 的布局上,所以不存在 textView 为 null 的问题;user 的空检查也不需要了,如果 user 为 null,user.name 就会被分配默认值 null。
-
- 数据更新的时候不再需要重复调用 setText 了。
如何使用
使用 DataBinding 之前记得在 build.gradle 中启用 DataBinding:
arduino
android {
...
buildFeatures {
dataBinding true
}
}
在想要使用 DataBinding 的布局的根布局上右键,选择 Show Context Actions,然后选择 Convert to data binding layout。
这里我们实现一个显示学生信息列表的功能,此外还包含两个按钮,一个用于添加学生,一个用于删除学生。
activity_main.xml:
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="clickPresenter"
type="com.example.test.MainActivity.ClickPresenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:text="添加学生"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{clickPresenter::addStudent}"/>
<Button
android:text="删除学生"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{clickPresenter::removeStudent}"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_student_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
在 activity_main.xml 中配置添加学生和删除学生按钮分别调用 ClickPresenter 中的 addStudent() 和 removeStudent() 方法。
MainActivity 中的代码如下:
java
public class MainActivity extends AppCompatActivity {
private StudentListAdapter mStudentListAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding mActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mActivityMainBinding.rvStudentList.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
mStudentListAdapter = new StudentListAdapter(getStudentList());
mActivityMainBinding.rvStudentList.setAdapter(mStudentListAdapter);
mActivityMainBinding.setClickPresenter(new ClickPresenter());
}
private List<Student> getStudentList() {
List<Student> list = new ArrayList<>();
list.add(new Student("小明","001"));
list.add(new Student("小红","002"));
list.add(new Student("小刚","003"));
list.add(new Student("小强","004"));
return list;
}
public class ClickPresenter {
public void addStudent(View view) {
Toast.makeText(MainActivity.this, "addUser", Toast.LENGTH_SHORT).show();
mStudentListAdapter.addData(new Student("小张","005"));
mStudentListAdapter.notifyDataSetChanged();
}
public void removeStudent(View view) {
Toast.makeText(MainActivity.this, "removeUser", Toast.LENGTH_SHORT).show();
if(mStudentListAdapter.getItemCount() > 0){
mStudentListAdapter.remove(0);
mStudentListAdapter.notifyDataSetChanged();
}
}
}
}
在 MainActivity 中使用 DataBindingUtil 的 setContentView() 方法绑定了上面的布局文件并对 ClickPresenter 中的方法进行了实现。
Student 类的代码如下:
java
public class Student extends BaseObservable {
private String name;
private String id;
public Student(String name, String id) {
this.name = name;
this.id = id;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
notifyPropertyChanged(BR.id);
}
}
展示学生信息对应的布局文件为:item_student.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="student"
type="com.example.test.Student" />
</data>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.id}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name}"/>
</LinearLayout>
</layout>
RecyclerView 还需要结合 Adapter 来展示列表信息,代码如下:
java
public class StudentListAdapter extends RecyclerView.Adapter<StudentListAdapter.StudentItemViewHolder> {
private List<Student> mStudentList;
public StudentListAdapter(List<Student> list) {
mStudentList = list;
}
@NonNull
@Override
public StudentItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_student, null);
return new StudentItemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull StudentItemViewHolder holder, int position) {
holder.getItemStudentBinding().setStudent(mStudentList.get(position));
holder.getItemStudentBinding().executePendingBindings();
}
@Override
public int getItemCount() {
return mStudentList.size();
}
public void addData(Student student) {
mStudentList.add(student);
}
public void remove(int index) {
mStudentList.remove(index);
}
public static class StudentItemViewHolder extends RecyclerView.ViewHolder {
private ItemStudentBinding binding;
public StudentItemViewHolder(View itemView) {
super(itemView);
binding = DataBindingUtil.bind(itemView);
}
public ItemStudentBinding getItemStudentBinding() {
return binding;
}
}
}
在 StudentItemViewHolder 中使用 DataBindingUtil 的 bind() 方法来绑定列表的 item 布局,并返回 ItemStudentBinding 实例,这样 StudentItemViewHolder 持有 ItemStudentBinding 实例就行了,不需要持有里面的每一个 View 了,同时省略了 findViewById 的代码。在 StudentListAdapter 中也不需要分别给 item 中的控件设值了,直接调用 ItemStudentBinding 实例的 setStudent() 即可。
Binding Adapters
现在要显示学生头像,我想用 Glide,头像来源于图片链接,同时还需要提供占位图。由于没办法直接在 ImageView 布局中配置图片链接和占位图,使用 DataBinding 该如何实现?
这时候就可以使用 BindingAdapter,添加如下代码:
java
public class CommonUtils {
@BindingAdapter({"app:imageUrl", "app:placeHolder"})
public static void loadImageFromUri(ImageView imageView, String imageUri, Drawable placeHolder) {
Glide.with(imageView.getContext())
.load(imageUri)
.placeholder(placeHolder)
.into(imageView);
}
}
然后就可以直接在 ImageView 的布局中配置 imageUrl 和 placeHolder 了:
xml
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:imageUrl="@{student.url}"
app:placeHolder="@{@drawable/student_icon}"/>
这样只要配置了 imageUrl 和 placeHolder,注解 @BindingAdapter 对应的方法就会被调用。如果你希望配置了 imageUrl 和 placeHolder 中的某一个,就调用该方法,需要添加 requireAll = false,代码如下:
kotlin
@BindingAdapter({"app:imageUrl", "app:placeHolder"}, requireAll = false)
结合 LiveData
前面 variable 中的 Student 类必须要继承 BaseObservable 或使用 ObservableField,还要添加注解 @Bindable、调用notifyPropertyChanged(BR.name),目的是为了在 student.setName(name) 时通知对应的 View 更新。
现在有了 LiveData,LiveData 使用数据驱动,可以不需要使用 BaseObservable 或 ObservableField 了,并且还可以自动管理生命周期,避免内存泄漏。代码如下:
xml
// student.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">
<data>
<variable
name="viewmodel"
type="com.example.test.ScheduleViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.studentname}"/>
</LinearLayout>
</layout>
kt
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: StudentBinding = DataBindingUtil.setContentView(this, R.layout.student)
binding.lifecycleOwner = this
binding.viewmodel = ViewModelProvider(this)[ScheduleViewModel::class.java]
}
}
kt
class ScheduleViewModel : ViewModel() {
private val _studentname = MutableStateFlow("")
val studentname: StateFlow<String> = _studentname
init {
viewModelScope.launch {
delay(5000)
_studentname.value = "小王"
}
}
}
可以看到不需要调用 LiveData 的 observe(owner,observer) 了,DataBinding 帮我们完成了数据驱动。