文章目录
一、前言
本篇记录下关于DataBinding
双向绑定的问题。双向绑定的意思是Modle的数据变化会引起UI刷新,UI的刷新也会反向引起Modle的改变。
二、属性的定义
以下是简单的示例
ObservableUserModel.kt
class ObservableUserModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
var isCheck = false
@Bindable
fun getRememberMe(): Boolean {
return isCheck
}
fun setRememberMe(value: Boolean) {
// Avoids infinite loops.
if (isCheck != value) {
isCheck = value
// React to the change.
// saveData()
Log.e("YM--","---->rememberMe更改后的值:$value")
// Notify observers of a new value.
notifyPropertyChanged(BR.rememberMe)
}
}
private var state = false
var timerRunning: Boolean
@Bindable get() {
return state
}
set(value) {
// These methods take care of calling notifyPropertyChanged()
Log.e("YM--","---->timerRunning更改后的值:$value")
state = value
notifyPropertyChanged(BR.timerRunning)
}
@get:Bindable
var newCheck = false
set(value) {
field = value
Log.e("YM--","---->newCheck更改后的值:$value")
// notifyPropertyChanged(BR.newCheck)
}
@Bindable
var newCheck2 = false
set(value) {
field = value
Log.e("YM--","---->newCheck2更改后的值:$value")
}
fun updateName(newName: String) {
name = newName
notifyPropertyChanged(BR.name)
}
override fun addOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.remove(callback)
}
/**
* Notifies observers that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
activity_main.xml
<?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:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="observableModel"
type="com.example.myapplication.ObservableUserModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatCheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择"
android:checked="@={observableModel.newCheck2}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/update_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
以上ObservableUserModel
中凡是包含Bindable
定义的属性,均可使用双向绑定,在set
函数里面可以监听数据改变并执行下一步操作。里面的notifyPropertyChanged()
函数视情况调用,比如该值改变后触发其它属性的UI刷新。
三、使用自定义特性的双向数据绑定
之前讲过自定义属性的用法和双向绑定的基本用法。这里将两者进行结合起来进行使用。
InverseObservableUserModel.kt
class InverseObservableUserModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
@Bindable
var inverseContent = "默认值Inverse"
fun updateInverseContent(newValue: String) {
inverseContent = newValue
notifyPropertyChanged(BR.inverseContent)
}
override fun addOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.remove(callback)
}
/**
* Notifies observers that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
InverseBindAdapter.kt
//双向绑定的自定义属性拓展
object InverseBindAdapter {
@BindingAdapter("time")
@JvmStatic fun setTime(view: AppCompatEditText, newValue: String) {
// Important to break potential infinite loops.
if (view.text?.toString() != newValue) {
view.setText(newValue)
}
}
//该函数会在使用InverseBindingListener进行调用
// 而且在使用时候绑定的名字必须为timeAttrChanged,命名规则用AttrChanged做后缀,除非使用event进行指定,
// 例如 @InverseBindingAdapter(attribute = "time", event = "timeAttrChanged")
// InverseBindingAdapter必须与InverseBindingListener配套使用,不可单独使用
// 如果不使用双向绑定的话,如"@={inverseObservableModel.inverseContent}"形式的话则不会调用该函数
@InverseBindingAdapter(attribute = "time", event = "timeAttrChanged")
@JvmStatic fun getTime(view: AppCompatEditText) : String {
Log.e("YM","--->接收返回值")
return view.text?.toString() ?: ""
}
@BindingAdapter("timeAttrChanged")
@JvmStatic fun setListeners(
view: AppCompatEditText,
attrChange: InverseBindingListener
) {
// Set a listener for click, focus, touch, etc.
view.onFocusChangeListener = View.OnFocusChangeListener { focusedView, hasFocus ->
val textView = focusedView as TextView
Log.e("YM","--->焦点更改:$hasFocus")
if (hasFocus) {
// Delete contents of the EditText if the focus entered.
textView.text = ""
} else {
// If the focus left, update the listener
attrChange.onChange()
}
}
}
}
activity_main.xml
<?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:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="inverseObservableModel"
type="com.example.myapplication.InverseObservableUserModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTop="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
time="@={inverseObservableModel.inverseContent}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
四、转换器
有时候使用的数据需要根据情况进行转换,跟前文使用的转换器需求一样,不过代码略有差异,之前使用的转换器为@BindingConversion
,现在使用@InverseMethod
进行转换,代码如下
InverseObservableUserModel.kt
class InverseObservableUserModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
@Bindable
var inverseContent = "默认值Inverse"
@Bindable
var inverseDate = Date().time
fun updateInverseContent(newValue: String) {
inverseContent = newValue
notifyPropertyChanged(BR.inverseContent)
}
override fun addOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback
) {
callbacks.remove(callback)
}
/**
* Notifies observers that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
ConverterAdapter.java
这里为什么使用java,是因为DataBinding
编译后的代码是Java类型,如果使用kotlin
,kotlin
的基本类型是kotlin
的,不是Java
的,所以会导致类型不匹配,一直编译不通过
public class ConverterAdapter{
//这里需要注意@InverseMethod("stringToDate")中的参数需要创建一个同名的函数,其中入参,返回参数要相互换下位置
@InverseMethod("stringToDate")
public static String dateToString(AppCompatEditText editText,long newValue){
Log.e("YM"," dateToString类型转换");
return "新的日期";
}
public static long stringToDate(AppCompatEditText editText,String newValue){
Log.e("YM"," stringToDate类型转换");
return 0;
}
}
activity_main.xml
<?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:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.example.myapplication.ConverterAdapter"/>
<variable
name="inverseObservableModel"
type="com.example.myapplication.InverseObservableUserModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/update_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
time="@={ConverterAdapter.dateToString(input,inverseObservableModel.inverseDate)}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
五、其余内容
以下内容主要是引用的官方资料,略微注意一下就行,另外就是在xml中是可以使用context
属性的,类型为Context
。无需引入额外的库,直接使用对象就行
六、参考链接
-
双向数据绑定 | Android 开发者 | Android Developers
-
https://github.com/android/databinding-samples