JetPack-(6)-DataBinding 数据绑定

1.什么是数据绑定

DataBinding是在xml中以声明的方式,将布局中的UI组件绑定与数据源绑定。它可以引用类,并且使用里面的变量或方法,而且控件自身也可以和bean中的某个字段进行关联(包括单项关联或双向关联),这样做的好处是,当任何一方发生变化的时候,另一方会自动更新自己的值。

相比传统的findViewById()查找控件 然后再设置值。DataBinding就显得简洁很多,使得页面与布局之间的耦合度进一步降低,减少了重复的findViewById这一类的代码。

DataBinding具有如下几点优势:

  1. 项目更加简洁,可读性更高。部分与UI控件相关的代码可以在布局文件中完成
  2. 不再需要findViewById()方法,大量减少了Activity中的代码
  3. 布局文件可以包含简单的业务逻辑。UI控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互
  4. 可以提高应用性能,有利于防止内存泄漏以及避免发送Null指针异常
  5. 可以更好的实现MVVM架构

DataBinding 将 Layout布局文件中的UI组件与数据模型Model进行绑定:

  • 当 用户 通过UI组件 修改数据时,会将数据自动更新到数据模型中
  • 数据模型中的数据改变时,会自动更新到UI组件中。

2.基础使用:

DataBinding的绑定有两种方式,单向数据绑定和双向数据绑定:

  • 单向数据绑定:例如,当Model中的数据改变时,通知改变页面上的值。
  • 双向数据绑定: 当Model中的数据改变时,通知改变页面上的值。当页面上的数据修改之后,通知改变Model中的值。

需要用到的依赖:

在Model内的build.gradle中, android/defaultConfig配置块中,配置如下内容:

ini 复制代码
android {
    defaultConfig {
    
        buildFeatures{
            dataBinding = true
        }
        
    }
}

2.1单向绑定:

2.1.1:定义数据类(Model):

arduino 复制代码
public class Student {
   private String name;
   private int age;

   public Student(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }
}

2.1.2 布局转换

在build.gradle构建脚本中,设置dataBinding{enable = true}后,第一时间点击右上角的SyncNow同步代码,否则下面无法进行布局转换。

在布局文件中,将鼠标放在左上角第一个字符位置,按下 Alt+回车 组合键,

按下Alt + 回车 组合键,会弹出下面的菜单, 有 Convert to data binding layout 选项,选择该选项,就可以键布局文件切换成DataBinding布局文件

如果没有该选项,说明在build.gradle构建脚本中 设置了dataBinding{ eabled = true}后,没有同步代码:

转为DataBinding布局前的结构:

ini 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</androidx.constraintlayout.widget.ConstraintLayout>

转换为DataBinding布局后的结构:

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>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

2.1.3 在xml中定义data标签,设置数据类型信息

ini 复制代码
<?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="student"
            type="com.wzj.databindingdemo.Student" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/text_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{student.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.4"
            tools:text="Tom" />

        <TextView
            android:id="@+id/text_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(student.age)}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/text_name"
            app:layout_constraintVertical_bias="0.2"
            tools:text="18" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

在上述代码中,通过data标签,设置了数据类型信息,

在布局文件中,为组件设置tools:text属性,该属性只能在Design视图中查看,方便开发调试,不会显示在最终的应用中。

在布局文件中,使用@{student.变量名},获取该数据类型对象的成员,并设置到布局组件中

3.1.4 注意事项

需要注意的是,如果字段的类型是int类型,如student.age,设置 android:text属性时,需要将其转换为String类型

3.1.5 效果展示

2.2双向绑定:

双向绑定是建立在单向绑定的基础上,实际开发中用到双向绑定的地方没有单向绑定多。

例如EditText,用户编辑EditText,字段自动同步,当字段有变化时,EditText自动更新

2.2.1 代码示例:

定义一个Student类:

typescript 复制代码
public class Student {
   private String name;
   private int age;

   public Student(String name, int age) {
       this.name = name;
       this.age = age;
   }
   public String getNameText(){
       return "姓名"+name;
   }
   public String getAgeText(){
       return "年龄"+age;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }
}

2.2.2 通过BaseObservable实现双向绑定

在该类中设置了@Bindable注解,只要student对象中的name发生了变化,绑定的组件中的内容就会发生变化

如果要实现通过EditText修改数据模型的效果,需要再实现一个setStudentName()方法,该方法需要与之前使用@Bindable注解修饰的getStudentName()方法对应。

修改后需要调用notifyPropertyChanged(BR.xxx)通知数据模型进行变更 BR类是BaseObservable子类中由@Bindable注解修饰的方法生成的

BR类生成位置在:app\build\generated\ap_generated_sources\debug\out\com.databindingdemo\BR.java

2.2.3 布局文件设置(重点):

在EditText控件赋值时,需要使用 android:text="@={student.studentName}" 进行赋值,注意值为: @={student.studentName},比之前的数据绑定多了一个等号

2.2.4 在Activity中,

在Activity组件中,向布局中设置的对象类型是StudentViewModel类型的,不是Student类型的

2.2.5 执行结果:

逐个字母删除Tom,然后输入Jack

2.3 ObservableField实现数据模型Model与视图View双向绑定(优化上述例子)

在上面的做法中,存在一些弊端,首先我们的类必须继承自BaseObservable,另外,在Getter方法前还需要加上@Bindable标签,告诉DataBinding我们要绑定该字段,最后,还需要再Setter方法中手动调用notifyPropertyChanged()方法以通知观察者。

有一种更加简单的做法。那就是ObservableField,它能将普通对象包装成一个可观察的对象,ObservableField可以用于包装各种基本类型,集合数组,自定义类,实现代码如下:

上述代码,也能实现与BaseObservable双向绑定相同的效果。我们只通过ObservableField对象将数据给包装起来,并为字段编写了Getter和Setter方法。GetStudentName()方法在程序启动时被自动调用,当用户修改EditText内容时,setStudentName()方法被自动调用

DataBinding还提供了可观察集合ObservableArrayMap对象和ObservableArrayList对象

ObservableField与LiveData

我们发现ObservableField的使用方式和作用与LiveData很像。实际上,两者可以替换使用的,二者的区别在于,LiveData与生命周期相关,它通常在ViewModel中使用,并且需要在页面中通过observe()方法对变化进行监听。而双向绑定无需在页面中加入额外的代码,耦合度更低。

3.表达式语言

在布局文件中是通过 @{},语法写入控件中的,比如,TextView文本被设置为Student.的Name属性:

但是表达式的功能不止如此,我们还可以在表达式语言中使用以下运算符和关键字:

  • 算数运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 位移运算符 >> >>> <<
  • 比较运算符 == > < >= <= (注意,<需要转义为<)
  • instanceof
  • 分组运算符()
  • 字面量运算符-字符,字符串,数字,null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问[]
  • 三元运算符 age > 18 ? 未成年 : 已成年

3.字符串拼接函数

我们可以在数据类中定义字符串拼接函数,直接在DataBinding布局文件中,调用字符串拼接函数:

还是Student类, 在DataBinding布局文件中,声明

布局文件中的控件中:

  • 调用@{student.nameText()} 设置 "姓名 : tom"
  • 调用@{student.ageText()} 设置 "年龄 : 18"

4.绑定点击事件函数

首先在Student类中定义如下方法:

然后在xml的TextView的android:onClick属性中,设置@{student.onClick}点击事件

5.导入类(import) 变量,包含(使用include导入二级界面布局)

5.1 导入类 import

导入起始就跟使用类方法差不多,通过导入功能,可以在布局文件中引用类,就像在托管代码一样,可以在data元素下使用多个import元素,也可以不使用。

导入View类可以通过绑定表达式引用该类,如下图展示了如何引用View类的VISIBLE和GONE

注意类型别名

当类名有冲突的时候,其中一个类可以使用别名重命名.使用alias即可

当我们使用的时候,使用 myView 即可

导入其他类

导入的类型可用作变量和表达式中的类型引用。以下显示了用作变量类型的sutdent和List

ini 复制代码
<import type="com.wzj.databindingdemo.Student"/>
<import type="java.util.List"/>
<variable name="student" type="Student" />
<variable name="studentList" type="List&lt;Student" />

5.2 变量

变量就是我们使用的标签引入的属性,变量类型会在编译时进行检查。

ini 复制代码
<data>
      <import type="android.graphics.drawable.Drawable"/>
       <variable name="user" type="com.example.User"/>
       <variable name="image" type="Drawable"/>
       <variable name="note" type="String"/>
</data>

5.3 使用include导入二级界面布局

对于include进行引用的布局文件,我们称为二级页面。DataBinding也支持从一级页面将数据传递到二级页面.

一级页面传递数据 A.xml

二级页面接收数据 B.xml

6.自定义BindingAdapter

借助@BindingAdapter注解,可以将自定义逻辑绑定到DataBinding布局中

例如:为ImageView控件绑定数据,传入一个url网络图片地址,该控件中显示网络图片,如果网络图片加载失败或者为空,则加载默认的本地资源。该操作必须自定义一段代码逻辑进行实现,使用简单的数据绑定无法实现该功能。

6.1 基础使用

首先编写处理图片的BindingAdapter类,在该方法中通过Glide加载网络图片

需要注意的是:ImageViewBindingAdapter中的方法均为静态方法。第一个参数为调用者本身(也就是当前的UI控件),第二个参数是布局文件在调用该方法时传递过来的参数。我们在@BindingAdapter中添加的一个别名image,布局文件正是通过别名来调用该方法的。

在布局文件中调用BindingAdapter

首先,需要在布局变量中定义一个String,用于存放网络图片的地址,然后再ImageView通过别名,及我们再ImageViewBindingAdapter文件中定义好的别名image,来调用静态方法,布局表达式@{}中的参数,则是调用方法时传入的参数

需要注意,我们需要在布局文件最外层包含以下命名空间,才能调用自定义@BindingAdapter标签定义的静态方法:

在Activity中为布局变量赋值,也就是networkImage

结果如下:

6.2 方法重载

刚才我们已经自定义BindingAdapter类,让UI空间能够通过属性设置,来加载网络图片。现在,我们可以通过方法重载,让该静态方法支持显示项目资源文件中的图片,代码如下:

布局文件中:

在Activity中:

这样,BindingAdapter就能够显示项目资源文件中的图片了

6.3 多参数重载

我们可以将上面的两个方法,合并成一个方法,并且将两个参数同时传入方法中,当网络图片地址为空时,则显示imageResource参数所指定的图片:

在@BindingAdapter标签中,方法的别名设置以value={"",""}的形式设置,在该方法中,我设置了两个别名,及通过这两个别名都可以调用该静态方法,变量requireAll用于告诉DataBinding库这些参数是否都要赋值,默认为true即要全部赋值.

布局代码如下:

当networkImage为空时,ImageView会显示localImage所指定的图片.

方法是如何区分传入的资源是网络资源还是本地资源呢?Databinding是根据传递参数的类型来进行区分,比如说: networkImage是网络图片资源,那么就是String类型,方法就知道它传入的是网络资源图片,localImage是本地类型资源,那么就是int类型,方法就知道它传入的是本地资源图片。

6.4 可选旧值

BindingAdapter可以覆盖Android原先的控件属性。比如说,可以设置在每一个TextView的文本都加上后缀: -哈哈哈,代码如下:

xml中:

activity中:

实现效果:

6.5 对象转换

6.5.1 自动转换对象

6.5.2 自定义转换BindingConversion

与BindingAdapter类似 以下方法会将布局文件中所有以@{String},方式引用到的String变量加上后缀 Android

xml文件中的TextView:

最终效果:

注意:@BindingConversion会将布局文件中所有以@{String}方法引用到的String变量加上后缀 Android,但是分以下两种情况:

  1. 如果属性是Android自带的属性。比如说android:text,@BindingAdapter的优先级低于@BindingConversion,会优先调用@BindingConversion注解的方法
  2. 如果属性是自定义的。比如我们上面例子中的图片加载,image别名,这时候@BindingAdapter的优先级会高于@BindingConversion,会优先调用@BindingAdapter注解的方法。

7.DataBinding 在 RecyclerView中使用

7.1定义数据类Student

arduino 复制代码
public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

7.2 定义item布局

ini 复制代码
<?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="student"
            type="com.wzj.databindingdemo.Student" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="50dip">

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{student.name}"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.3"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Tom" />

        <TextView
            android:id="@+id/age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(student.age)}"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.7"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="18" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

7.3 定义适配器Adapter

在DataBinding布局中,绑定了 type="com.wzj.databindingdemo.Student" 类型的数据

数据绑定布局ItemBinding在自定义的ViewHolder类中维护,可以通过该类获取数据绑定布局,并为其设置数据

在onCreateViewHolder中,获取DataBinding布局,并将其设置给自定的ViewHolder

获取DataBinding布局,调用DataBindingUtil.inflate函数,获取item.xml对应的DataBinding布局文件类ItemBinding实例对象。

在onBindingViewHolder中

scala 复制代码
public class StudentAdapter extends RecyclerView.Adapter<StudentAdapter.ViewHolder> {

    private List<Student> list = Arrays.asList(
            new Student("Tom", 18),
            new Student("Jerry", 12),
            new Student("Mickey", 16),
            new Student("Donald", 14)
    );

//在onCrateViewHolder中,调用DataBindingUtil.inflate函数,获取rv_item.xml对应的DataBinding布局文件类,并设置给ViewHolder
    @NonNull
    @Override
    public StudentAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        RvItemBinding itemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.rv_item, parent, false);
        return new ViewHolder(itemBinding);
    }

    //在onBindViewHolder函数中,为每个列表条目设置数据
    @Override
    public void onBindViewHolder(@NonNull StudentAdapter.ViewHolder holder, int position) {
        holder.itemBinding.setStudent(list.get(position));
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    //在自定义的ViewHolder类中,维护了rv_item.xml布局文件对应的RvItemBinding数据绑定类实例对象,在构造函数中设置该itemBinding对象
    public class ViewHolder extends RecyclerView.ViewHolder {
        private RvItemBinding itemBinding;

        public ViewHolder(View itemView) {
            super(itemView);
        }

        public ViewHolder(RvItemBinding itemBinding) {
            super(itemBinding.getRoot());
            this.itemBinding = itemBinding;
        }
    }
}

7.4 activity_main.xml

7.5 Activity中

运行效果:

相关推荐
安冬的码畜日常44 分钟前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java2 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele2 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
DOKE3 小时前
VSCode终端:提升命令行使用体验
前端
xgq3 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081353 小时前
前端之路-了解原型和原型链
前端
永远不打烊3 小时前
librtmp 原生API做直播推流
前端