一、介绍
通过前面两篇文档,我们大概了解了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>
这里面有人会不明白<和>是什么意思,
正常格式:ArrayList<String>
databind:ArrayList<String>
所以(< 为<)左括号,(>为>)右括号
<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方法也是这样:
介绍完以上用法,会发现,这些好像都是单向绑定,什么才是双向绑定?其实很简单
四、双向绑定
介绍完单向绑定,其实大家已掌握了双向绑定。用周董的话说,全球音乐看话语,中文才是最屌。
双向绑定比单向绑定在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>("我的名字")
进行测试。
效果图
总结:
只要我们掌握了单向绑定,双向绑定自然也会解决。但是,在使用双向的时候需要注意,不同的场景如果存在多处引用,会导致数据错乱。所以,在使用的时候需要格外小心。