DataBinding

文章目录

    • 引入支持
    • 在布局文件中绑定指定的变量
      • 基本用法
      • 单向数据绑定
        • BaseObservable
          • 效果图
        • ObservableField
        • Observable Collections
      • 双向数据绑定
    • 其他用法
    • 符号的坑——&&和<

引入支持

在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持

android {
    dataBinding {
        enabled = true
    }
}

在布局文件中绑定指定的变量

  • 原来默认的布局是这样的
    DataBinding_第1张图片
  • 选中并Alt+Enter,选择“Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则
    DataBinding_第2张图片
  • 如图,多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型
  • 要实现 MVVM 的 ViewModel,就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道
    DataBinding_第3张图片

基本用法

  • 声明一个User实体类
public class User {
    private String Username;
    private String Password;

    public User(String name, String psw) {
        this.Username = name;
        this.Password = psw;
    }

    public String getUsername() {
        return Username;
    }

    public void setUsername(String username) {
        Username = username;
    }

    public String getPassword() {
        return Password;
    }

    public void setPassword(String password) {
        Password = password;
    }
}
  • data 标签里声明要使用到的变量名、类的全路径
<data>
        <variable
            name="userInfo"
            type="com.sky.databindingapplication.User" />
    </data>
  • 如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.* 包中的类会被自动导入,所以可以直接使用
<data>
        <import type="com.sky.databindingapplication.User"/>
        <variable
            name="userInfo"
            type="User"/>
    </data>
  • 如果存在 import 的类名相同的情况,可以使用 alias 指定别名
<data>
        <import type="com.sky.databindingapplication.User" />
        <import
            alias="TempUser"
            type="com.sky.databindingapplication.User" />
        <variable
            name="userInfo"
            type="User" />
        <variable
            name="tempUserInfo"
            type="TempUser" />
    </data>
  • 这里声明了一个 User 类型的变量 userInfo,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置 userInfo 的变量值同时使 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.sky.databindingapplication.User" />
        <variable
            name="userInfo"
            type="com.sky.databindingapplication.User" />

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_username"
            ...
            android:text="@{userInfo.username, default=username}"
            />

        <TextView
            android:id="@+id/tv_psw"
            ...
            android:text="@{userInfo.password, default=password}"
            />

    </LinearLayout>
</layout>
  • 通过 @{userInfo.name} 使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法。
  • 其中,由于@{userInfo.name}在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号。
  • 之后可以在 Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法,并为变量 userInfo 赋值
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        User user = new User("sky","123");
        binding.setUserInfo(user);
        binding.tvUsername.setText("pdd");

在这里插入图片描述

  • 此外,也可以通过 ActivityMainBinding的对象,直接获取到指定 ID 的控件
binding.tvUsername.setText("pdd");
  • 每个数据绑定布局文件都会生成一个绑定类,ViewDataBinding 的实例名是根据布局文件名来生成,将之改为首字母大写的驼峰命名法来命名,并省略布局文件名包含的下划线。控件的获取方式类似,但首字母小写

注意:如果先在xml中设置了text的内容:android:text="@{userInfo.password, default=defaultValue}"
那么就不能通过获取TextView实例的方法动态修改text内容了,如下

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.tvName.setText("pdd");

而应该通过设置userInfo的变量值来改变text内容

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("sky","123");
binding.setUserInfo(user);

以上实现数据绑定的方式,每当绑定的变量发生变化的时候,都需要重新向 ViewDataBinding 传递新的变量值才能刷新 UI 。接下来看如何实现自动刷新 UI

单向数据绑定

实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection

BaseObservable

BaseObservable 提供了 notifyChange()notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图

package com.sky.databindingapplication;

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

public class Goods extends BaseObservable {

    //如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    @Bindable
    public String name;

    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    private String details;
    private float price;

    public Goods(String name, String details, float price) {
        this.name = name;
        this.details = details;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);     //只更新本字段
    }

    @Bindable
    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
        notifyChange();     //更新所有字段
    }

    @Bindable
    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}

  • 写一个GoodsHandler 内部类,来写button对应的事件(用onClick也一样)
public class MainActivity extends AppCompatActivity {
    private Goods goods;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        goods = new Goods("book", "story book", 12);
        binding.setGoods(goods);
        binding.setGoodsHandler(new GoodsHandler());
    }

    public class GoodsHandler {

        public void changeName() {
            goods.setName("name" + new Random().nextInt(100));
            goods.setPrice(new Random().nextInt(100));
        }

        public void changeDetails() {
            goods.setDetails("details" + new Random().nextInt(100));
            goods.setPrice(new Random().nextInt(100));
        }
    }
}

  • 添加两个按钮用于改变 goods 变量的三个属性值,由此可以看出两个 notify 方法的区别。
<TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{goods.name, default=goodsname}"
            android:textSize="20dp"/>

        <TextView
            android:id="@+id/tv_details"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{goods.details, default=goodsdetails}"
            android:textSize="20dp"/>

        <TextView
            android:id="@+id/tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(goods.price), default=goodsprice}"
            android:textSize="20dp"/>

        <Button
            android:id="@+id/btn_name_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="改变name和price"
            android:onClick="@{() -> goodsHandler.changeName()}"/>

        <Button
            android:id="@+id/btn_details_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="改变details和price"
            android:onClick="@{() -> goodsHandler.changeDetails()}"/>

注意:price的数据类型是float,而这里setText()只能传String类型的值,需要用String.valueOf(goods.price),将float类型的数转成String类型。否则将会报错
报错详情 -> android.content.res.Resources$NotFoundException: String resource ID #0x0

效果图

DataBinding_第4张图片
可以看到,name 视图的刷新没有同时刷新 price 视图,而 details 视图刷新的同时也刷新了 price 视图

  • 实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback
  • 当中 propertyId 就用于标识特定的字段
        goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == com.leavesc.databinding_demo.BR.name) {
                    Log.e(TAG, "BR.name");
                } else if (propertyId == com.leavesc.databinding_demo.BR.details) {
                    Log.e(TAG, "BR.details");
                } else if (propertyId == com.leavesc.databinding_demo.BR._all) {
                    Log.e(TAG, "BR._all");
                } else {
                    Log.e(TAG, "未知");
                }
            }
        });

ObservableField

我们刚刚介绍的通知UI更新的方法是用Goods类继承自BaseObservable,然后在getter上添加注解、在setter中添加notify方法,这感觉总是有点麻烦,步骤繁琐,于是,Google推出ObservableFields类,使用它我们可以简化我们的Model类,如:

public class ObservableGoods {
    public final ObservableField<String> name = new ObservableField<>();
    public final ObservableField<String> details = new ObservableField<>();
    public final ObservableFloat price = new ObservableFloat();
}
  • Button和普通的Button一样
<TextView
            android:id="@+id/tv_ob_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableGoods.name, default=obgoodsname}"
            android:textSize="20dp"/>

        <TextView
            android:id="@+id/tv_ob_details"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableGoods.details, default=obgoodsdetails}"
            android:textSize="20dp"/>

        <TextView
            android:id="@+id/tv_ob_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(observableGoods.price), default=obgoodsprice}"
            android:textSize="20dp"/>

        <Button
            android:id="@+id/btn_ob"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="改变ob"/>
  • 在onCreate中这么改
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        observableGoods = new ObservableGoods();
        observableGoods.name.set("phone");
        observableGoods.details.set("iphoneX");
        observableGoods.price.set(5180);
        binding.setObservableGoods(observableGoods);
        binding.btnOb.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_ob:
                observableGoods.name.set("toothbrush");
                observableGoods.details.set("philips");
                observableGoods.price.set(299);
        }
    }

DataBinding_第5张图片
当然ObservableField中传入的泛型可以是Java中的基本类型,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明。其他类型效果也和ObservableField是一样的,如:

public class User{
    public final ObservableField<String> userName = new ObservableField<>();
    public final ObservableField<Integer> userPassword = new ObservableField<>();
    public final ObservableInt userAge = new ObservableInt();
}

Observable Collections

dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap,当其包含的数据发生变化时,绑定的视图也会随之进行刷新

双向数据绑定

双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据

很简单,和单项数据绑定基本一样,只是多了一个等号android:text="@={observableGoods.name}"
看以下例子,当 EditText 的输入内容改变时,会同时同步到变量ObservableGoods

<TextView
            android:id="@+id/tv_ob_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{observableGoods.name, default=obgoodsname}"
            android:textSize="20dp"/>
            
<EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={observableGoods.name}"/>

DataBinding_第6张图片

其他用法

其他用法

  1. 字符串拼接
    如activity_main中如下属性,
android:text="@{`姓名:` + user.name}"
android:text="@{`年龄:` + String.valueOf(user.age)}"
  1. 三目运算
    如下属性,设置的性别gender值为0为男,1为女的三目运算。
android:text="@{user.gender == 0 ? `性别:男` : `性别:女`}"
  1. 使用控件
    使用binding对象可获取布局文件中的各个对象,根据控件设置的id来获取。如
<Button
            android:id="@+id/btn_cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="长按删除信息"/>

使用btnCancel设置长按事件:

        binding.btnCancel.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Toast.makeText(getApplicationContext(),"长按了",Toast.LENGTH_SHORT).show();
                return true;
            }
        });
  1. 点击事件写法
    传入OnClickListener的变量。
 <variable
            name="listener"
            type="android.view.View.OnClickListener"/>

在Button中给android:onClick设置listener的变量。

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="提交信息"
            android:onClick="@{listener}"/>

通过setListener传入点击监听给listener对象。

        binding.setListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"点击了",Toast.LENGTH_SHORT).show();
            }
        });

符号的坑——&&和<

要注意的坑:&& 和 <
有些情况下我们需要双重判断也就是&&正常情况下我们这样写觉得没有问题,但是&&始终会报错,强制编译运行就出问题像下面这种错误
DataBinding_第7张图片
那是因为&&,< 在databinding中做逻辑判断使用时 &&需要转译为:&& < 需要转译为:< 修改后:

                android:text="@{coin>=30&&coin < 60?`未完成`:``}"

参考文章

  1. https://www.jianshu.com/p/572822d9eff9
  2. https://www.jianshu.com/p/6994efc44dae
  3. https://www.jianshu.com/p/bd9016418af2

你可能感兴趣的:(Android)