(二)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格式所以要转义。用< 代表<;用&gt代表>(这些转义符,同样支持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中不能直接使用<>符号,所以:&lt;代表<,&gt;代表>-->
        <variable
            name="map"
            type="Map&lt;String,Object&gt;" />
        <!-- List类型用法,使用带观察者的列表对象ObservableList 导包-->
        <import
            alias="List"
            type="androidx.databinding.ObservableList" />
        <!-- List类型用法 声明变量,因在xml中不能直接使用<>符号,所以:&lt;代表<,&gt;代表>-->
        <variable
            name="list"
            type="List&lt;String&gt;" />
    </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;
    }
    //userName@Bindable标志,然后会生成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中双向绑定变量通过 @={变量名} 来使用,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章