Android双向绑定的好处就不再介绍了,直接上使用方法的代码。以下展示Demo1-3,使用方式从简到繁。
双向绑定使用Demo1:简单双向绑定
我们使用ObservableField与EditView展示双向绑定。使用方式如下,将此ObservableField<String>与一个EditView进行双向绑定(使用@={}),当视图EditView中的文字变化时,ObservableField中数据也会随着变化。ObservableField中的数据变化,也会造成EditView中文字的变化。在此,ObservableField中的数据与EditView中的文字是相同的,不好展示ObservableField中发生的变化,所以,我们使用一个额外的TextView展示ObservableField中发生变化的数据
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:tools="http://schemas.android.com/tools"
xmlns:MyView="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="userVM"
type="com.example.jetpacklearn.viewModel.UserViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:layout_width="60dp"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:text="@={userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/showName"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/showName"
android:text="@{userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/userName"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
在这里,使用@={}进行双向绑定,即EditView中文字的变化会同步到数据源userVM.name,userVM.name中数据的变化会同步到EditView。以下为UserViewModel的代码:
class UserViewModel:ViewModel() {
var name:ObservableField<String> = ObservableField("")
var age:ObservableInt = ObservableInt(10)
}
Activity的代码如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var layoutInflater = LayoutInflater.from(this)
var dataBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(dataBinding.root)
var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
dataBinding.userVM = userViewModel
}
}
运行结果如图:
双向绑定使用Demo2:自定义数据结构的双向绑定
在实际中,我们使用的可能是自定义的数据,而非基本类型数据,这样就不能直接使用ObservableInt等基本数据类。因此,在自定义数据类时,需要使数据类继承BaseObservable,以便在数据变化时通知观察者。下面以一个Person数据类进行展示,首先,定义一个Person的data类。然后,定义一个PersonViewModel,其内部含有Person对象,并继承BaseObservable,以便在数据变化时,可以发送通知。
Person数据类
data class Persion(var name:String,var age:Int) {
}
PersonViewModel类
class PersionViewModel : BaseObservable(){
var persion = Persion("Jack",19)
@Bindable
fun getPersionName():String{
return persion.name
}
fun setPersionName(name:String){
if(name!=persion.name){
//避免设置数据时的无限循环
persion.name = name
//发送数据变化通知
notifyPropertyChanged(BR.persionName)
}
}
@Bindable
fun getPersionAge():Int{
return persion.age
}
fun setPersionAge(age:Int){
if(age!=persion.age){
persion.age = age
notifyPropertyChanged(BR.persionAge)
}
}
}
在这个类中,要使用@Bindable注解get方法,并且要在set方法中调用notifyPropertyChanged,以在数据变化时,通知观察者。BR.persionName是编译之后生成的。@Bindable的作用是告诉数据绑定系统如何读取PersonViewModel中的数据。PersonViewModel的使用代码如下:
<?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:tools="http://schemas.android.com/tools"
xmlns:MyView="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="userVM"
type="com.example.jetpacklearn.viewModel.UserViewModel" />
<variable
name="persionVM"
type="com.example.jetpacklearn.viewModel.PersionViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:layout_width="60dp"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:text="@={userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/showName"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/showName"
android:text="@{userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/userName"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toEndOf="parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/persionLinearLayout"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@+id/userName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/persionName"
android:text="@={persionVM.persionName}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/showPersionName"
android:text="@{persionVM.persionName}"/>
</LinearLayout>
</LinearLayout>
</layout>
Activity类如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var layoutInflater = LayoutInflater.from(this)
var dataBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(dataBinding.root)
var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
dataBinding.userVM = userViewModel
var persionVM = PersionViewModel()
dataBinding.persionVM = persionVM
}
}
运行结果如下:
双向绑定Demo3:自定义视图的双向绑定
在实际中,可能需要自定义View,这时,可能想对自定义View中的某个属性进行双向绑定。例如,定义一个记录点击次数的MyTextView,此视图中包含一个用来记录点击次数的变量。视图代码如下:
MyTextView代码
class MyTextView: androidx.appcompat.widget.AppCompatTextView {
//记录点击次数
private var clickTime:Int = 0
constructor(context:Context):super(context){}
constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){}
constructor(context: Context,attributeSet: AttributeSet,defStyleAttr:Int):super(context,attributeSet,defStyleAttr){}
fun setClickTime(newValue:Int){
if(clickTime!=newValue){
clickTime = newValue
}
}
fun getClickTime():Int{
return clickTime
}
}
首先,需要设置clickTime属性的get和set方法,因为在双向绑定中既要读数据,又要写数据。然后,使用注解告诉数据绑定系统如何从MyTextView中读写数据。代码如下:
//告知数据绑定系统如何写clickTime属性
@BindingMethods(value = [BindingMethod(type = MyTextView::class, attribute = "clickTime",method = "setClickTime") ])
//告知数据绑定系统调用哪个方法读取clickTime属性。此注解需要一个method,默认是get+属性名的方法,在此使用默认值
//此注解需要一个event,默认为属性名+AttrChanged,在此使用默认值
@InverseBindingMethods( value = [ InverseBindingMethod(type = MyTextView::class,attribute = "clickTime") ] )
class MyViewBindingAdapter {
}
//BindingAdapter、InverseBindingAdapter与上面的BindingMethods、InverseBindingMethods作用相同,
//都是告知数据绑定系统如何读写MyTextView中的clickTime,所以,只需设置一组就好
//@BindingAdapter("clickTime")
//fun setClickTime(myTextView: MyTextView,clickTime:Int){
// myTextView.setClickTime(clickTime)
//}
//@InverseBindingAdapter(attribute = "clickTime",event = "clickTimeAttrChanged")
//fun getClickTime(myTextView: MyTextView):Int{
// return myTextView.getClickTime()
//}
//通过此注解,来为属性添加一个监听器listener,监听属性的变化,此listener是编译器自动生成的。
//总的来说,最终还是要通过一些onClick、onTouch、TextWatcher等监听方法来监听自定义属性的变化。
@BindingAdapter("app:clickTimeAttrChanged")
fun setClickTimeAttrChanged(myTextView: MyTextView, listener: InverseBindingListener){
//监听属性clickTime的变化
myTextView.setOnClickListener {
var clickTime = myTextView.getClickTime() + 1
myTextView.setClickTime(clickTime)
//使用listener发送属性变化通知
listener.onChange()
}
}
为了在xml文件中使用clickTime属性,需要先在values文件夹下建立一个styleable文件,文件如下:
<resources>
<declare-styleable name="MyTextView" >
<attr name="clickTime" format="integer"></attr>
</declare-styleable>
</resources>
这样就可以在xml文件中对MyTextView的clickTime进行双向绑定了,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:tools="http://schemas.android.com/tools"
xmlns:MyView="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.example.jetpacklearn.BindingKt"/>
<variable
name="userVM"
type="com.example.jetpacklearn.viewModel.UserViewModel" />
<variable
name="persionVM"
type="com.example.jetpacklearn.viewModel.PersionViewModel" />
<variable
name="clickNum"
type="androidx.databinding.ObservableInt" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:layout_width="60dp"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:text="@={userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@+id/showName"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/showName"
android:text="@{userVM.name}"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/userName"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toEndOf="parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/persionLinearLayout"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@+id/userName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/persionName"
android:text="@={persionVM.persionName}"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/showPersionName"
android:text="@{persionVM.persionName}"/>
</LinearLayout>
</LinearLayout>
<com.example.jetpacklearn.view.MyTextView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="@+id/myTextView"
android:text="@{BindingKt.intToString(clickNum)}"
android:gravity="center"
MyView:clickTime = "@={clickNum}"
app:layout_constraintTop_toBottomOf="@+id/persionLinearLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
使用MyView:clickTime="@={clickNum}",进行双向绑定,并使用text="@{BindingKt.intToString(clickNum)}"来展示点击的次数。
Activity类如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var layoutInflater = LayoutInflater.from(this)
var dataBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(dataBinding.root)
var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
dataBinding.userVM = userViewModel
var persionVM = PersionViewModel()
dataBinding.persionVM = persionVM
//设置MyTextView的clickTime的数据源
var clickNum = ObservableInt(0)
dataBinding.clickNum = clickNum
}
}
这样,当我们点击MyTextView时,通过之前设置的点击监听器,使clickTime加一。然后,发出属性变化通知,使clickNum(ObservableInt)的数据也加一。最后,使用android:text属性,展示clickNum中的数据。
运行结果如下:
总结,在上面,分别展示了简单的数据双向绑定、自定义数据结构的双向绑定、自定义视图属性的双向绑定。
相关博客: