学习笔记:DataBinding(二): 数据绑定

本节学习使用可观察的数据对象进行单向和双向数据绑定

使用DataBinding,我们可以使用之前我们已知的原始的基本类型、引用类型数据,但这些数据的改变不会使UI自动更新。

DataBinding为我们提供了数据驱动视图的可观察数据对象: objects(对象), fields(字段), collections(集合)。使用它们绑定UI,当这些对象的属性发生改变,UI会自动更新。


Observable fields

DataBinding为我们包装好的基本类型的可观察字段:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

我们自己也可通过ObservableField 泛型来申明其他类型:

class User {// 普通类型var firstName: String? = null // 使用ObservableField自己封装var lastName: ObservableField<String>? = null// 使用官方提供已包装的var age: ObservableInt? = null
}
class MyHandler(private val mContext: Context) {// 伴生对象,相当于Java中的静态成员companion object {private val TAG = "MyHandler"}fun onClick(user: User) {Toast.makeText(mContext, "点击了:" + user.firstName, Toast.LENGTH_SHORT).show()user.firstName = "你好"// 注意调用,不是= ,而是相当于Java中断getLastName().set(String)// 直接=,相当于setLastName(),不会自动刷新的user.lastName!!.set("DataBinding")// !!非空断言运算符将任何值转换为非空类型,若该值为空则抛出异常,因为user.age是可空对象,所以需要使用user.age!!.set(2)}}

<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data><import type="com.example.databindingsample.User" /><variablename="user"type="User" /><variablename="myHandler"type="com.example.databindingsample.MyHandler" />data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{user.lastName}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{String.valueOf(user.age)}" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="@{() -> myHandler.onClick(user)}"android:text="属性更改" />LinearLayout>layout>
class MainActivity : AppCompatActivity() {private var binding: ActivityMainBinding? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)val user = User()user.firstName = "helllo"user.lastName = ObservableField("databinding")user.age = ObservableInt(1)binding!!.user = userbinding!!.myHandler = MyHandler(this)}
}

效果图:

可以看到,我们使用ObservableField和ObservableInt的字段属性值改变时,视图也随之改变了,而原始的数据类型是属性值改变是不会引起视图更新的。


Observable collections

  • ObservableArrayMap
  • ObservableArrayList

<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data><import type="com.example.databindingsample.User" /><variablename="list"type="androidx.databinding.ObservableList<String>" /><variablename="map"type="androidx.databinding.ObservableMap<String,String>" /><variablename="myHandler"type="com.example.databindingsample.MyHandler" />data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{map[`first`]}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text='@{map["second"]}' /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{list[0]}" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="@{() -> myHandler.onClick(map,list)}"android:text="属性更改" />LinearLayout>layout>
class MyHandler(private val mContext: Context) {companion object {private val TAG = "MyHandler"}fun onClick(map: ObservableMap<String,String>,list: ObservableList<String>) {Toast.makeText(mContext,"点击了",Toast.LENGTH_SHORT).show()map.put("first","android")map.put("second","iOS")list[0] = "GO"}}
class MainActivity : AppCompatActivity() {private var binding: ActivityMainBinding? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)val observableMap = ObservableArrayMap<String, String>().apply {put("first","kotlin")put("second","java")}val observableList = ObservableArrayList<String>().apply {add("kotlin")add("java")}binding!!.map = observableMapbinding!!.list = observableListbinding!!.myHandler = MyHandler(this)}
}

效果如下:


Observable objects

DataBinding给我们提供了Observable,让类继承Observable后,当类对象属性变化时通过notifyPropertyChangednotifyChange来通知UI刷新。

class User : BaseObservable() {// 将@Bindable注解用于属性getter// @Bindable注解应该应用于一个可观察类的任何getter访问方法。Bindable将在BR类中生成一个字段来标识该字段,用于当该属性发生改变时@get:Bindablevar firstName: String = ""set(value) {field = value// 只更新本字段
//            notifyPropertyChanged(BR.firstName)// 更新所有字段notifyChange()}@get:Bindablevar lastName: String = ""set(value) {field = value
//            notifyPropertyChanged(BR.lastName)}@get:Bindablevar age: Int = 0set(value) {field = value
//            notifyPropertyChanged(BR.age)}
}

<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data><import type="com.example.databindingsample.User" /><variablename="user"type="User" /><variablename="myHandler"type="com.example.databindingsample.MyHandler" />data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><TextViewandroid:id="@+id/tv_name"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@{user.firstName}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{user.lastName}" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:text="@{String.valueOf(user.age)}" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="@{() -> myHandler.onClick(user)}"android:text="属性更改" />LinearLayout>layout>
class MainActivity : AppCompatActivity() {private var binding: ActivityMainBinding? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)binding!!.user = User().apply {firstName = "1"lastName = "2"age = 3}binding!!.myHandler = MyHandler(this)}
}
class MyHandler(private val mContext: Context) {companion object {private val TAG = "MyHandler"}fun onClick(user: User) {Toast.makeText(mContext,"点击了",Toast.LENGTH_SHORT).show()user.apply {firstName = "4"lastName = "5"age = 6}}
}

效果:

注意:

  • 在Kotlin中使用注解,需要在app build.gradle中添加Kotlin注解插件:apply plugin: 'kotlin-kapt', 否则会编译失败:compileDebugKotlin FAILED
  • BR类似于R文件,DataBinding自动生成的一个资源ID文件;另外这个id值并不固定,每次build可能都不一样

Two-way data binding

以上我们学习的DataBinding使用,都是使用的单向绑定,也就是数据驱动视图,下面我们来学习,数据与视图双向驱动,数据改变可以使UI刷新,UI改变也可以让数据更新,也就是DataBinding的双向数据绑定。

先看下面这样一个小栗子:

Model类和UI中的Checkbox,我们希望Checkbox的选中状态可以根据Model的isChecked属性刷新,同时Checkbox选中状态主动改变时,也更新Model的isChecked属性

我尝试用之前单向绑定的方式去做了一下,如下代码:

class Model : BaseObservable(){@get:Bindablevar isChecked: Boolean = falseset(value) {field = valuenotifyChange()}
}
class MyHandler(private val mContext: Context) {fun onCheckedChanged(isChecked: Boolean,model: Model){if(model.isChecked != isChecked){model.isChecked = isCheckedToast.makeText(mContext,"视图驱动数据:" + model.isChecked,Toast.LENGTH_SHORT).show()}}fun onClick(model: Model){model.isChecked = !model.isCheckedToast.makeText(mContext,"数据驱动视图:" + model.isChecked,Toast.LENGTH_SHORT).show()}}

<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:bind="http://schemas.android.com/apk/res-auto"><data class="MainActivityBinding"><variablename="model"type="com.example.databindingsample.Model" /><variablename="myHandler"type="com.example.databindingsample.MyHandler" />data><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><CheckBoxandroid:id="@+id/cb"android:layout_width="wrap_content"android:layout_height="wrap_content"android:checked="@{model.isChecked}"android:onCheckedChanged="@{(buttonView,isChecked) -> myHandler.onCheckedChanged(isChecked,model)}"/><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:text="Model更改属性值"android:onClick="@{() -> myHandler.onClick(model)}"/>LinearLayout>layout>
class MainActivity : AppCompatActivity() {private var binding: MainActivityBinding? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = DataBindingUtil.setContentView(this, R.layout.activity_main)binding!!.model = Model()binding!!.myHandler = MyHandler(this)}
}

之所以采用数据类继承BaseObservable方式实现,是因为我在尝试使用ObservableBoolean如下方式时,却出现了让我费解的情况,代码如下:

class Model{var isChecked: ObservableBoolean = ObservableBoolean(false)
}
class MyHandler(private val mContext: Context) {fun onCheckedChanged(isChecked: Boolean,model: Model){if(model.isChecked.get() != isChecked){// 关键问题在这行代码model.isChecked = ObservableBoolean(isChecked)Toast.makeText(mContext,"视图驱动数据:" + model.isChecked.get(),Toast.LENGTH_SHORT).show()}}fun onClick(model: Model){model.isChecked.set(!model.isChecked.get())Toast.makeText(mContext,"数据驱动视图:" + model.isChecked.get(),Toast.LENGTH_SHORT).show()}}

就这样其他代码不变,我的本意是在视图驱动视图时,更新Model的isChecked属性通过model.isChecked = ObservableBoolean(isChecked) ,使用 = 直接赋值,不调用set()刷新了,就出现了如下的情况,单独点击CheckBox状态更新没有问题,单独点击Button更新CheckBox也没问题,但是只要点击了CheckBox再去点击Button就无法再刷新视图了,如下效果图:

model.isChecked = ObservableBoolean(isChecked) 替换成 model.isChecked.set(isChecked) 就正常了

这个问题应该是因为DataBinding最初在布局中给CheckBox和Button绑定的是同一个Model的isChecked,也就是同一个ObservableBoolean, 而 = 符号,使得CheckBox绑定了另一个ObservableBoolean,所以后面二者无法联动。 而set是更新ObservableBoolean的值,所以没问题

进入正题,下面来看这个让我浪费很长时间的问题,DataBinding怎么使用双向绑定来搞定的,更改代码如下:

<CheckBoxandroid:id="@+id/cb"android:layout_width="wrap_content"android:layout_height="wrap_content"android:checked="@={model.isChecked}" android:onCheckedChanged="@{(buttonView,isChecked) -> myHandler.onCheckedChanged(isChecked,model)}"/>
class MyHandler(private val mContext: Context) {// 这个监听只为输出属性值fun onCheckedChanged(model: Model){Toast.makeText(mContext,"视图驱动数据:" + model.isChecked.get(),Toast.LENGTH_SHORT).show()}fun onClick(model: Model){model.isChecked.set(!model.isChecked.get())Toast.makeText(mContext,"数据驱动视图:" + model.isChecked.get(),Toast.LENGTH_SHORT).show()}}

效果如下:

如上代码MainActivity代码不变,使用@={}符号即可完成双向绑定,就代替了我之前的onCheckedChanged监听,和其中的一些处理,如果不是为了Toast属性值,xml中的onCheckedChanged是不用设置的

细心的话会发现,点击CheckBox时输出的Model.isChecked是反的,其实没问题,是因为CheckBox的onCheckedChanged回调在Model.isChecked属性改变之前,拿到的还是之前的值


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部