1.官方文档
https://developer.android.com/topic/libraries/data-binding/binding-adapters
https://developer.android.com/topic/libraries/data-binding/two-wa
2.双向绑定
2.1 单向的问题
默认是单向绑定,控件与用户的交互(如输入新的名字,点单选框和复选框)不会自动修改对应的绑定数据对象,要在代码里手动处理相应的控件事件,在事件函数里修改数据对象。如:
1 val data = Data() 2 lateinit var binding : Way2Binding 3 val nameWatcher = object : TextWatcher{ 4 override fun afterTextChanged(p0: Editable?) { 5 val txt = p0.toString() 6 data.name = txt 7 binding.invalidateAll() 8 } 9 override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 10 override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 11 } 12 13 fun initBinding(){ 14 binding.data = data 15 binding.normalEdt.addTextChangedListener(nameWatcher) 16 }
2.2 使用双向绑定
使用双向绑定可以简化这个问题,减少相应代码。
用 @={}
表示可接收属性的数据更改并同时监听用户更新.
1 <EditText 2 ... 3 android:text='@={data.name,default=@string/data_name}' 4 .../>
这样当用户在这个EditText上输入新的内容后,直接更新到绑定的数据对象。
注意: @={} 不可以使用格式化的@string/xx
2.3 自定义的属性使用双向绑定
a.自定义属性的 setXXX
1 @BindingAdapter("dataName") 2 @JvmStatic fun setDataName(edt : EditText, txt : String){ 3 edt.setText(txt) 4 }
b.自定义属性的 getXXX
1 @InverseBindingAdapter(attribute = "dataName") 2 @JvmStatic fun getDataName(edt : EditText) : String{ 3 return edt.text.toString() 4 }
注意:attribute = 不可少,否则编译不过。
c.编写属性变化监听器及想要监听的事件
1 @BindingAdapter("dataNameAttrChanged") 2 @JvmStatic fun setListener(edit : EditText, listener: InverseBindingListener?) { 3 Log.e("dataNameAttrChanged","txt = ${edit.text.toString()}") 4 var txt = "" 5 edit.addTextChangedListener(object : TextWatcher{ 6 override fun afterTextChanged(p0: Editable?) { 7 Log.e("dataNameAttrChanged","afterTextChanged") 8 if (txt != p0.toString()){ 9 listener?.onChange() 10 txt = p0.toString() 11 } 12 } 13 override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 14 override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} 15 }) 16 }
- InverseBindingListener 是属性变化监听器
- 这个 EditText 监听的事件是 textChanged
注意: listener?.onChange() 容易死 循环,要判断下原内容与新内容,不一样才调用这个函数。上述代码中的高亮部分。
d.布局文件中使用
<EditText app:dataName='@={data.name}' ... >
2.4 类型转换的双向绑定
使用类型转换时,也可以使用双向绑定,但是要指定一个反向转换的函数。用 @InverseMethod("转换函数名") 来声明。
1 @file:JvmName("Converter") 2 3 package com.example.databind 4 import androidx.databinding.InverseMethod 5 6 7 class A (var name: String) 8 9 @InverseMethod("a2String") 10 fun string2a(string: String) : A{ 11 return A(string) 12 } 13 14 fun a2String(a : A) : String{ 15 return a.name 16 }
在布局文件中
<EditText android:text='@={Converter.a2String(a)}' />
2.5 支持双向绑定的内置属性
控件 | 属性 | 绑定适配器所在的类 |
---|---|---|
AdapterView |
android:selectedItemPosition android:selection |
AdapterViewBindingAdapter |
CalendarView |
android:date |
CalendarViewBindingAdapter |
CompoundButton |
android:checked |
CompoundButtonBindingAdapter |
DatePicker |
android:year android:month android:day |
DatePickerBindingAdapter |
NumberPicker |
android:value |
NumberPickerBindingAdapter |
RadioButton |
android:checkedButton |
RadioGroupBindingAdapter |
RatingBar |
android:rating |
RatingBarBindingAdapter |
SeekBar |
android:progress |
SeekBarBindingAdapter |
TabHost |
android:currentTab |
TabHostBindingAdapter |
TextView |
android:text |
TextViewBindingAdapter |
TimePicker |
android:hour android:minute |
TimePickerBindingAdapte |
3.自定义绑定
3.1 系统定义的绑定方法
假设在绑定布局文件中使用app:aaaaa 的属性,绑定库自动尝试查找方法 setAaaaa(arg)
。
- 按app:aaaaa 时传递的参数, 找到参数匹配或兼容的那个方法setAaaaa
- 不会考虑属性的命名空间
- 搜索方法时仅使用属性名称和类型
- 假设类Student包含 setTtttt 这个方法,那么可以直接在布局文件中使用 app:ttttt 这个属性。
3.2 自定义绑定的方法
- a.重新绑定属性与对应的方法
1 @BindingMethods(value = [ 2 BindingMethod( 3 type = ImageView::class, 4 attribute = "android:tint", 5 method = "setImageTintList")])
上述代码把 android:tint 这个属性绑定到了setImageTintList方法。官方文档中并没有给出这段应该放哪里,放在类里面编译不过,通过官方完整示例中发现它是全局的。
- b.修改系统已经定义好的方法
1 @androidx.databinding.BindingAdapter("android:paddingLeft") 2 @JvmStatic 3 fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { 4 if (oldPadding != newPadding) { 5 view.setPadding(12, 6 view.getPaddingTop(), 7 view.getPaddingRight(), 8 view.getPaddingBottom()) 9 } 10 }
重新定义了 android:paddingLeft属性,注意,调用的时候 android:paddingLeft='@{16}' 才匹配这个方法,android:paddingLeft="16dp" 还是系统的那个方法。
出现冲突时,自定义的绑定方法会替换数据绑定库提供的默认方法.
重新定义系统已经存在的属性没意义,这个方法要以 '@{16}‘ 这种 方式调用,android:paddingLeft="16dp" 无效
- c.把多个属性联合在一起,绑定个方法
1 @JvmStatic 2 @androidx.databinding.BindingAdapter(value = ["imageUrl", "error"], requireAll = false) 3 fun loadImage(view: ImageView, url: String, error: Drawable) { 4 Picasso.get().load(url).error(error).into(view) 5 }
requireAll = false 的含义是 单独使用imageUrl,error其中一个属性的时候,就绑定这个方法,true就是必需同时使用这两个属性的时候才绑定这个方法。
使用
<ImageView android:id="@+id/imageView2" android:layout_width="64dp" android:layout_height="64dp" app:error='@{@drawable/error}' app:imageUrl='@{"sdfsd.com/fef.ppppjnpng"}' />
3.3 类型转换 @BindingConversion
作用:使用这个 注解可以定义一个类型A到类型B在转换函数,函数名的格式为
convertAToB,如 convertStringToData
1 @JvmStatic
2 @androidx.databinding.BindingConversion
3 fun convertStringToData(name : String) = Data()