databinding介绍

1、DataBinding介绍

DataBinding 是 Google 在 Jetpack 中推出的一款数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源。使其维护起来更加方便,架构更明确简介。所谓的绑定,是绑定什么呢?

  • 数据直接绑定到UI上,数据改变时UI自动更新
  • UI上的数据绑定到变量中,当数据(如EditText中的数据)改变时自动更新

DataBinding非常适合用于MVVM模式中充当View和ViewModel的双向通讯的工具,引入DataBinding之后,我们可以少写很多的例如findViewById()之类的代码。

2、DataBinding使用介绍

2.1 引入DataBinding

要使用DataBinding只需要在build.grade中添加以下代码

android {
    ...
    dataBinding{
        enabled = true
    }
    ...
    }

2.2 布局文件格式转换

首先我们需要将传统的ViewGroup布局转换为固定的layout布局,然后在布局文件中声明所需要用到的数据实体类,布局文件格式如下。

<?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=".MainActivity3">

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

传统布局转换为layout布局也很简单,选中根布局,点击鼠标右键->show Context Action ->convert to data binding layout即可自动生成。生成的布局分为两个部分,数据区布局区,数据区内声明我们所需要用到的变量,布局区和传统的布局一样。

data

标签内进行变量声明和导入等

variable

标签进行变量声明

import

标签导入需要的类

    <data>
        <import type="com.example.model.Book" alias="mBook"/>
        <import type="com.example.Utils.StringUtils"/>
        <variable
            name="book"
            type="mBook"/>
        <variable
            name="books"
            type="androidx.databinding.ObservableList&lt;String>" />
        <variable
            name="bgColor"
            type="java.lang.String" />
    </data>

2.3 Activity和Fragment中使用databinding

Activity

public class MainActivity extends AppCompatActivity{
    
    private ActivityMainBinding mainBinding;
    
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        ...
    }
}

Fragment

public class TopFragment extends Fragment {

    private FragmentTopBinding topFragmentBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        topFragmentBinding = DataBindingUtil.inflate(inflater,R.layout.topfragment,container,false);
        
        return topFragmentBinding.getRoot();
    }
}

注意:

上面的步骤2.2和2.3不能颠倒,因为只有在布局转换成layout样式后,databinding才会根据布局文件的名字自动生成一个对应的binding类,例如ActivityMainBinding和FragmentTopBinding,一般命名方式就是id+binding。如果我们xml文件命名不太规范,不好推测生成的类名,可以在build/generated/data_binding_base_class_source_out目录下查看生成的类。

在这里插入图片描述

生成的ActivityMainBinding类中持有布局文件中一些view的引用,一般来说有三类,根布局,含有**@{}绑定的view**,含有id属性的view。于是我们可以直接通过binding对象去获取视图中的一些view对象,而不需要在经过findViewById()的查找过程。

2.4 数据绑定

在data标签中,variable标签内声明变量名和类型,在布局中通过@{}进行引用,它支持的类型有

  • 引用类型,比如一个类或者对象

  • 基本数据类型,不需要导入对应的包,直接使用即可,由于databinding不会自动做类型转换,因此需要我们自己手动的转换,比如text标签内用String.valueOf()进行转换,rating标签内使用Float.valueOf()进行转换。既然我们可以调用String类中的方法,那么是不是也可以调用其他类中的一些方法呢?当然也是可以的,比如我们定义了一个静态的方法

    public class StringUtils {
        public static String toUpperCase(String origin){
            return origin.toUpperCase();
        }
    }
    

    然后同样在xml文件中通过import 进行导入

    <import type="com.example.Utils.StringUtils"/>
    

    接着就可以像调用String.valueOf()一样去调用它了。

  • 数据集合类型

    • 集合成员的获取方式有get和[ ]两种方式,比如我们传入一个集合books,可以通过"@{books.get(0).author}“或者”@{books[0].author}"来获取它的属性,如果值为null, 可以通过default 来指定其属性。

      android:rating="@{book.rating,default=3.0}"
      

      类似的用法还可以通过??或者是?:来实现, ??会取第一个不为null的值

      android:text="@{book.rating != null ? book.rating : book.defaultRating}"
      
      android:text="@{user.name ?? book.defaultRating}"
      
    • map类型的结构也可以通过get和[ ]两种方式获取,不过需要注意单引号和双引号的成对使用

      "@{map.get(‘zhangsan’)}"或者是’@{map.get(“zhangsan”)}’

        <TextView
            android:id="@+id/text_publish_house"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.publishHouse}"
            app:layout_constraintBottom_toTopOf="@+id/guideline9"
            app:layout_constraintStart_toEndOf="@+id/textView7"
            app:layout_constraintTop_toTopOf="@+id/guideline8" />

        <TextView
            android:id="@+id/text_pages"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(book.pages)}"
            app:layout_constraintBottom_toTopOf="@+id/guideline10"
            app:layout_constraintStart_toEndOf="@+id/textView8"
            app:layout_constraintTop_toTopOf="@+id/guideline9" />
                
         <RatingBar
            android:id="@+id/ratingBar"
            android:layout_width="248dp"
            android:layout_height="62dp"
            android:numStars="5"
            android:stepSize="0.5"
            android:rating="@{book.rating,default=3.0}"
            app:layout_constraintBottom_toTopOf="@+id/guideline4"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/textView4"
            app:layout_constraintTop_toTopOf="@+id/guideline2" />

别名

如果我们导入的包名字有冲突,可以在import标签内为其设置一个别名

    <data>
        <import type="com.example.model.Book" alias="mBook"/>
        <import type="com.example.Utils.Book" alias="sBook"/>
        <variable
            name="book"
            type="mBook"/>
    </data>

在MainActivity中,导入视图创建ActivityMainBinding对象,通过ActivityMainBinding对象进行数据的注入,对于标签内声明的变量,ActivityMainBinding都会提供一个set方法。

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding mBinding;
    private ArrayList<Book> books;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        Book book = new Book("第一行代码(第三版)","郭霖","人民邮电出版社","2020-4",704,99,"9787115524836",3.0);
        books.add(book);
        mBinding.setBook(book);
    }
}

接下来在Activity中,我们就不需要在写findViewById()之类的代码,直接通过 mBinding就可以对布局中的对象进行引用。

隐式引用属性

在view上引用其他view的属性

<layout ...>
  <data>
    <import type="android.view.View"/>
  </data>
  <RelativeLayout ...>
    <CheckBox android:id="@+id/seeAds" .../>
    <ImageView android:visibility="@{seeAds.checked ? View.VISIBLE : View.GONE}" .../>
  </RelativeLayout>
</layout>

include标签和ViewStub标签

如果我们在视图中通过include标签或者ViewStub标签导入了其他的布局,同样可以通过 dataBinding 来进行数据绑定,首先将需要包含的布局同样改为layout样式

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <import type="com.leavesc.databinding_demo.model.User" />
        <variable
            name="userInfo"
            type="User" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#acc">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="20dp"
            android:text="@{userInfo.name}" />

    </android.support.constraint.ConstraintLayout>
</layout>

然后在包含进来的地方将数据传递给它

<include
    layout="@layout/view_include"
    bind:userInfo="@{userInfo}" />

ViewStub的使用方式类似,这里就不叙述了。

2.5 数据对象

要实现数据变化时自动更新UI,有三种方法可以使用,分别是BaseObservable,ObservableField,ObservableCollection,下面一一介绍一下。

  • BaseObservable

自定义一个数据对象,必须继承自BaseObservable,BaseObservable是Android原生的已经封装好的一个类,通过被观察者的方式对数据进行监听,通过注解@Bindable将成员绑定到视图,如果是private类型的成员,则将Bindable加在它的get方法上,视图中就可以引用了。

public class Book extends BaseObservable {
    // 图片id
    @Bindable
    public String imageId;
    // 书名
    @Bindable
    public String bookName;
    // 作者
    @Bindable
    public String author;
    // 出版社
    @Bindable
    public String publishHouse;
    // 出版年
    @Bindable
    public String publishYear;
    // 页数
    @Bindable
    public int pages;
    // 定价
    @Bindable
    public double price;
    // ISBN
    @Bindable
    public String isbn;
    // 评分
    @Bindable
    public Double rating;
    // 出厂价
    private double rarePrice;
    // 私有对象绑定到它的get方法上 
    @Bindable
    private double getRarePrice(){
        return this.rarePrice;
    }
    
    ...
    }

那如何在数据更新时自动刷新UI呢?它提供了notifyChange()和notifyPropertyChanged(BR.xx)两个方法进行刷新,前者会刷新所有的成员,后者只会刷新对应的数据成员。示例如下

    public void setBookName(String bookName) {
        this.bookName = bookName;
        notifyPropertyChanged(BR.bookName);  // 只更新bookName这一个属性
    }

这时候如果在其他地方调用了book.setName()这个函数,UI就会被自动更新。上面使用到了BR这个类,它是什么呢,联想到R类,它应该也是系统自动生成的,我们在build/generated/ap_generated_sources/debug/out对应包的目录下可以找到这个类

public class BR {
  public static final int _all = 0;

  public static final int author = 1;

  public static final int name = 2;
    
    ...
}

在这个类中自动对我们使用Bindable注解的成员进行编号。

监听器回调

当我们在UI中改变控件的某些数据时,如果想要接收到这个改变,那么可以给其添加上一个监听器

        book.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == BR.author){
                    // TODO
                }
            }
        });
  • ObservableField

    还记得LiveData吗?既然都是观察者模式,那么也可以将需要被观察的数据封装起来,从而不用继承BaseObservale也可以实现数据的绑定,官方提供了对基本类型的封装,如:ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可以通过ObservableField<>泛型来申明其它类型。

于是,我们上面的Book类也可以改造成

public class Book {
    // 图片id
    public ObservableInt imageId;
    // 书名
    public ObservableField<String> bookName;
    // 作者
    public ObservableField<String> author;
    // 出版社
    public ObservableField<String> publishHouse;
    // 出版年
    public ObservableField<String> publishYear;
    // 页数
    public ObservableInt pages;
    // 定价
    public ObservableDouble price;
    // ISBN
    public ObservableField<String> isbn;
    // 评分
    public ObservableFloat rating;
    
    ...
}

注意:

Book类中必须为每个成员增加上get和set方法,一般我们在其中进行类型转换,比如上面的bookName我们将它转换成String类型再返回,总而言之,不需要向外界暴露出原本的类型。

  • ObservableCollection

observableCollection只是databinding提供的用于替换Java原生的List和Map,分别对应ObservableList和ObservableMap,其用法和原理和ObservableField没有太大的区别。

<variable
   name="books"
   type="androidx.databinding.ObservableList&lt;String&lt;" />

2.6 databinding支持的 表达式语法

上面我们就已经使用了字段访问,数组访问[],方法调用,三元运算符等,下面就总结一下xml文件中还支持哪些语法格式。

  • 算术运算符 + - / * %

  • 字符串连接运算符 +

  • 逻辑运算符 && ||

  • 二元运算符 & | ^

  • 一元运算符 + - ! ~

  • 移位运算符 >> >>> <<

  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)

  • instanceof

  • 分组运算符 ()

  • 字面量运算符 - 字符、字符串、数字、null

  • 类型转换

  • 方法调用

  • 字段访问

  • 数组访问 []

  • 三元运算符 ?:

3、数据的双向绑定

所谓双向绑定,就是当UI上的数据改变时也能更新到变量中,比如我们例子中的Book 星级评分rating, 可以让用户修改评星的同时也能同步到book对象中。使用方式很简单,只需要在原来的绑定方式上加一个=即可。

android:rating="@={book.rating,default=3.0}"

实例演示

。。。

4、 BindingAdapter

当某些属性需要自定义处理逻辑的时候可以使用BindingAdapter, 比如我们可以重新定义TextView的setText方法,让所有的英文全部转换为小写,又例如我们在Book类中保存了图片的id,如果直接在ImageView中直接将图片id传递给ImageView的src属性,图片是不会进行显示的,我们可以通过BindingAdapter重定义ImageView的setImageResource方法,图片就可以显示了。

图片显示例子

1、在Book类中重定义一个静态方法来处理ImageView的setImageResource方法 ,app是一个命名空间,可以不需要。

    @BindingAdapter({"app:imageUrl"})
    public static void getTransImageView(ImageView imageView, int res){
        imageView.setImageResource(res);
    }

2、在布局文件中,ImageView标签内通过app:imageUrl设置图片

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="185dp"
            android:layout_height="344dp"
            android:layout_marginStart="16dp"
            android:layout_marginLeft="16dp"
            app:imageUrl="@{book.imageId}"
            app:layout_constraintBottom_toTopOf="@+id/guideline2"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/textView" />

还可以使用BindingAdapter来自定义多个属性,下面看一个例子

public class ImageViewAdapter {
    /**
     * 定义多个属性
     * @param view
     * @param url
     * @param placeholder
     * @param error
     */
    @BindingAdapter(value = {"imageUrl", "placeholder", "error"})
    public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
        RequestOptions options = new RequestOptions();
        options.placeholder(placeholder);
        options.error(error);
        Glide.with(view).load(url).apply(options).into(view);
    }
}

在布局中使用上面定义的三个属性

<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_marginTop="10dp"
    app:imageUrl="@{`https://goss.veer.com/creative/vcg/veer/800water/veer-136599950.jpg`}"
    app:placeholder="@{@drawable/icon}"
    app:error="@{@drawable/error}"/>

此时,三个属性全部使用才能 BindingAdapter 才能正常工作,如果使用了其中的一些属性则不能正常编译通过,那么如何在自定义多个属性而正常使用其中的部分属性呢,@BindingAdapter 注解还有一个参数 requireAll ,requireAll 默认为 true,表示必须使用全部属性,将其设置为 false 就可以正常使用部分属性了,此时,自定义多个属性时要配置 注解 @BindAdapter 的 requireAll 属性为 false,参考如下:

// requireAll = false
@BindingAdapter(value = {"imageUrl", "placeholder", "error"},requireAll = false)
public static void loadImage(ImageView view, String url, Drawable placeholder, Drawable error) {
    RequestOptions options = new RequestOptions();
    options.placeholder(placeholder);
    options.error(error);
    Glide.with(view).load(url).apply(options).into(view);
}

在布局中,我们只使用到部分属性也不会编译报错了。

5、BindingConversion

有时候,在设置属性值的时候,会发生一些类型不匹配的情况,比如控件RatingBar的rating属性需要设置的float类型的值,但是我们的Book类中rating属性被设置为Double类型了,Book类在多个地方用到,如果要去改类型的话需要修改很多地方,这时就可以直接通过BindingConversion来进行方便的转换了。又比如 android:background 属性接收的是一个 Drawable 当我们在 databinding 的表达式中设置了一个颜色值,此时就需要 @BindingConvert

背景颜色转换例子

假设我们想要直接通过颜色名字设置布局背景,比如通过

mainBinding.setBgColor("skyBlue");

背景文件中是这样使用的

<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>
        <import type="com.example.model.Book" alias="mBook"/>
        <import type="com.example.Utils.StringUtils"/>
        <variable
            name="book"
            type="mBook"/>
        <variable
            name="books"
            type="androidx.databinding.ObservableList&lt;String>" />
        <variable
            name="bgColor"
            type="java.lang.String" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/text_title"
        android:background="@{bgColor}"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
        
 </layout>

这时候一定会报错,因为background不接受一个String类型的参数,那么我们可以提供一个

函数对其进行转换

@BindingConversion
    public static ColorDrawable convertStringToDrawable(String color){
        if (color.equals("skyBlue")){
        return new ColorDrawable(Color.parseColor("#57faff"));
        }
        return new ColorDrawable(Color.parseColor("#000000"));
}

databinding会自动查找使用@BindingConversion注解的函数进行转换,能使用@BindingConversion注解的函数有几个要求

  • 必须是静态的
  • 只能由一个参数
  • 被该注解标记的方法,被视为dataBinding的转换方法。
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值