欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

(二)Android Jetpack 组件之数据绑定库 (DataBinding)

程序员文章站 2022-06-08 16:31:49
...

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,如下所示:
(二)Android Jetpack 组件之数据绑定库 (DataBinding)
从此有了这个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。

输出结果:
(二)Android Jetpack 组件之数据绑定库 (DataBinding)

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;
    }
    //aaa@qq.com标志,然后会生成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);
    }
}

输出结果:
(二)Android Jetpack 组件之数据绑定库 (DataBinding)

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中双向绑定变量通过 @={变量名} 来使用,

相关标签: Android Mvvm架构