一、介绍

        通过前面两篇文档,我们大概了解了databinding的工作方式,view的初始化,recycleview的使用。但是这些UI都离不开数据的填充,数据的修饰。

在说到数据绑定,好多开发者平时在工作中也经常听到databinding的数据绑定有简单、单向绑定、双向绑定,玄幻莫测,不敢下手。甚至有些新手听完果然放弃。接下来我会通过代码讲解databinding的数据绑定和使用,包括map、list、和用户自定义类,让复杂的事件简单化,人人都可以掌握好并使用

数据绑定

        数据绑定分为两种,一种是系统支持的,还有一种是databind的数据,接下来我们分梁部分介绍

1、系统默认数据类型

基础数据

String、int、float、double,boolean

<data class="MyDataInfo">

        <variable
            name="name"
            type="String" />

        <variable
            name="age"
            type="int" />

        <variable
            name="bodyH"
            type="float" />

        <variable
            name="income"
            type="double" />

        <variable
            name="sex"
            type="boolean" />
    </data>

简单类型,我们直接使用即可

 布局最终引用:

<TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{name}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(age)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(bodyH)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(income)}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(sex)}" />

注意:

任何在布局中的value都需要被处理成字符串类型,也就是说boolean或者double不能直接@{double}@{boolean},这种是错误的

正确:

<TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(sex)}" />

错误:

<TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{sex}" />

注意:一些静态方法可以在布局中直接引用,这个后期会单独介绍

2、ArrayList、HashMap等聚合数据绑定

聚合列的数据,在data下方也是支持的,常用的有以下三种:map、list、sparseArry。

如何使用呢?因为data下方的数据都是要指定泛型的,所以这三种数据都是支持泛型,所以你必须要指定泛型。

泛型格式:


<variable
    name="list"
    type="ArrayList<String>" />


在type里面指定泛型。type格式=ArrayList<String>

这里面有人会不明白&lt;和&gt;是什么意思,

正常格式:ArrayList<String>

databind:ArrayList&lt;String&gt;

所以(&lt;   为<)左括号,(&gt;为>)右括号

<data class="MyDataInfo">

        <import type="java.util.HashMap" />
        <import type="java.util.ArrayList" />
        <import type="android.util.SparseArray"/>

        <variable
            name="key"
            type="String" />

        <variable
            name="index"
            type="int" />

        <variable
            name="map"
            type="HashMap<String,Object>" />

        <variable
            name="list"
            type="ArrayList<String>" />

        <variable
            name="arry"
            type="SparseArray<String>" />

    </data>

在这里面,需要注意的是所有key和index最好动态设置,否则不方便业务开展

如何在view中绑定data数据:

<TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{arry.get(index)}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{list.get(index)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{map[key]}" />

数据设置:

var map = HashMap<String, Any>()
        map.put("name", "map你好")
        databind.key = "name"
        databind.map = map
        var list = ArrayList<String>()
        list.add("list0index")
        list.add("list1index")
//        databind.list = list

        var spar=SparseArray<String>()
        spar.put(0,"0 value SparseArray")
        spar.put(1,"1 value SparseArray")
        spar.put(100,"100 value SparseArray")
        databind.arry=spar
        databind.index = 100

说明:

        如果你的list没有设置,即在databind中为null,即使你设置了key或者index,也不会被引用,如果你设置了data,index会引起数组越界,但是不会抛空指针

二、databind数据介绍

        以上我们讲解的是通过系统数据取完成,但是我们在使用databinding的时候,是想使用他的数据特性,单向绑定、双向绑定。

        我们在上面介绍的数据绑定,都不涉及到,接下来我们要讲解通过databind提供的方式,进行单向绑定和双向绑定

1、单向绑定

什么叫单向绑定:

单向绑定就是data发生改变,会自动通知UI刷新,但是UI内容发生改变后,将不会引用data发生改变。

数据绑定有两种方法,第一种全家桶,第二种,用户自定义

第一种:全家桶模式BaseObservable、Bindable

BaseObservable:提供了数据更新的机制,可以通过notifyPropertyChanged(int field)和notifyChange()来完整数据的更新

Bindable:生成关联字段,形成关联图

如何使用:

1.继承:BaseObservable
2.通过Bindable注解绑定字段,改字段必须为public,否则绑定在get方法上

kotlin:

直接绑定在get方法上

class MySchool : BaseObservable() {

    @get:Bindable
    var name: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
           

        }

}

Java:

public class MySchool extends BaseObservable {


    private String schoolName="";

    @Bindable
    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
        notifyPropertyChanged(BR.schoolName);
    }
}

关于notifyPropertyChanged()和 notifyChange()

notifyPropertyChanged:只刷新指定的字段

notifyChange:刷新对象下所有bindable的字段

注意:

        有人先写set方法,BR无法找到指定的字段,是因为该字段还没有被bindable注释绑定,生成对应的关系图,所以要先bindable,在更新,否则找不到对应的字段

字段更新监听:addOnPropertyChangedCallback

不仅可以单向绑定,我们可以坚定当前绑定的数据对应的字段:


BaseObservable提供了一个addOnPropertyChangedCallback回调,可以在这里设置字段监听


var detail = MySchool()
        detail.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback(){
            override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
//                TODO("Not yet implemented")

            }
        })

2.自定义绑定字段ObservableField

通过上方,我们已知道全家桶配合的使用,但是databinding也提供了基础的绑定方法ObservableField,

BaseObservableField是基础类型,可以通过该类型指定泛型,也可以通过提供的其他数据类型进行绑定 ObservableFloat ObservableBoolean ObservableInt ObservableParcelable ObservableChar 聚合数据 ObservableArrayMap ObservableArrayList

接下来我们先从最基础的BaseObservableField基础用起

数据源:

class BaseFieldData {

    lateinit var name: ObservableField<String>
    lateinit var age: ObservableField<Int>
    lateinit var god: ObservableField<Dog>


}
class Dog() : Parcelable {

    lateinit var name: String

    lateinit var hostName: String

    constructor(parcel: Parcel) : this() {
        name = parcel.readString()!!
        hostName = parcel.readString()!!
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeString(hostName)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Dog> {
        override fun createFromParcel(parcel: Parcel): Dog {
            return Dog(parcel)
        }

        override fun newArray(size: Int): Array<Dog?> {
            return arrayOfNulls(size)
        }
    }


}

实现:

fun initData() {
        var name = ObservableField<String>("我的名字")
        var age = ObservableField<Int>(100)
        var dog = Dog()
        dog.name = "小黑"
        dog.hostName = name.get()!!;
        var isDog = ObservableField<Dog>(dog)
        var baseData = BaseFieldData()
        baseData.age = age
        baseData.god = isDog
        baseData.name = name
        dataBind.data = baseData


        dataBind.testClick.setOnClickListener {
            name.set("新名字")
            var newDog=Dog()
            newDog.name="我是小白"
            newDog.hostName=name.get().toString()
            isDog.set(newDog)
        }


    }

通过这样,我们已完成了担心绑定,每次我们只要更新name和age的变量值,UI会自动刷新

每个field通过set()来设置泛型参数,通过get来回去。

为什么我们没手动更新,系统确能自动完成UI的更新?

我们看下ObservableField的set()就可以知道

public void set(T value) {
    if (value != mValue) {
        mValue = value;
        notifyChange();
    }
}

当我们调用set()的时候,默认也调用的全局刷新。

第二种:采用databind封装好的

分析:

        封装好的ObservableChar已继承了BaseObservableField

public class ObservableChar extends BaseObservableField implements Parcelable, Serializable

所以只是在构造器类指定了内容类型,这样我们在使用的时候,不再需要设置泛型参数。


var size=ObservableDouble(12.0)


<import type="androidx.databinding.ObservableDouble"/>

        <variable
            name="size"
            type="ObservableDouble" />

     <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(size)}" />
dataBind.testClick.setOnClickListener {
        
            size.set(123.2)
        }

这些都很好处理,接下我们将介绍ObservableArrayList和ObservableArrayMap

三、数据集合:

ObservableArrayList和ObservableArrayMap

 介绍一个ObservableArrayMap:

布局中的data:

<import type="androidx.databinding.ObservableArrayMap" />

        <variable
            name="map"
            type="ObservableArrayMap<String,String>" />

        <variable
            name="key"
            type="String" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{map.get(key)}" />

代码中实现:

var map = ObservableArrayMap<String, String>()
        map.put("name", "zhangshan")
        dataBind.map = map
        dataBind.key = "name"


      dataBind.testClick.setOnClickListener {
     
            map.put("name", "修改过的")
        }



同理,map的put方法如下:


public V put(K k, V v) { V val = super.put(k, v); notifyChange(k); return v; }


也是调用了全局刷新,同理,ArryList的add方法也是这样:

Android databinding之数据单向与双向绑定详解与使用(三)_android studio

介绍完以上用法,会发现,这些好像都是单向绑定,什么才是双向绑定?其实很简单

四、双向绑定

        介绍完单向绑定,其实大家已掌握了双向绑定。用周董的话说,全球音乐看话语,中文才是最屌。

双向绑定比单向绑定在view绑定的时候,多一个=号

单向:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{dbValue}" />

双向:

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={dbValue}" />

这边采用了


var name = ObservableField<String>("我的名字")


进行测试。


Android databinding之数据单向与双向绑定详解与使用(三)_android_02

效果图

 总结:

               只要我们掌握了单向绑定,双向绑定自然也会解决。但是,在使用双向的时候需要注意,不同的场景如果存在多处引用,会导致数据错乱。所以,在使用的时候需要格外小心。