背景
在MVVM架构中,常用的做法是将View层与ViewModel层的数据进行双向绑定,
但目前比较容易搜索到的是单向绑定,
即使用LiveData的observe方法,
将ViewModel的变动通知给View层.
基于xml的双向绑定方案
是谷歌提供的数据绑定方法
需要先在gradle中配置使用databinding
当然ViewModel相关的lifecycle库也需要引入
1 2 3 4 5
| android { dataBinding { enabled = true } }
|
ViewModel里不需要特别写什么
1 2 3 4
| class MainActivityViewModel : ViewModel() { private var _et: MutableLiveData<String> = MutableLiveData() val et = _et }
|
在xml布局中需要使用更多一层嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <?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">
<data> <variable name="viewmodel" type="com.example.testdatabinding.MainActivityViewModel" /> </data>
<androidx.constraintlayout.widget.ConstraintLayout tools:context=".MainActivity">
<EditText android:id="@+id/input" android:text="@={viewmodel.et}" />
<TextView android:id="@+id/display" android:text="@{viewmodel.et}" />
</androidx.constraintlayout.widget.ConstraintLayout> </layout>
|
最后在activity中引入使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import <package>.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
val binding : ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewmodel = viewModel
binding.lifecycleOwner = this } }
|
即可实现双向的绑定
基于anko的双向绑定方案
databinding可能也是实现了各种onChangeListener才能够正常通信的,
anko无法提供 layout_id
因此想自行实现两个方向的做法.
两个单向绑定
-
ViewModel通知View
使用LiveData的observe方法
1 2 3
| mMainActivityViewModel.displayStr.observe(this, Observer<String> { show.text = it })
|
-
View层通知ViewModel层
可能需要手动在onChangeListener中写viewModel的postvalue语句来更新数据,
1 2 3 4 5
| textChangedListener { afterTextChanged { viewModel.et.postValue(it.toString()) } }
|
然后将这个手动操作的内容封装进扩展函数以减少代码量.
双向绑定
双向绑定时需要考虑对于TextView和EditText防止死循环上的不对称性
EditText通常先于viewModel变化,
TextView通常落后与viewModel变化.
想要执行的等效版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| verticalLayout { uiet = editText() { textChangedListener { afterTextChanged { viewModel.et.postValue(it.toString()) } } }
uitv = textView() { textChangedListener { afterTextChanged { if (viewModel.et.value != it.toString()) { viewModel.et.postValue(it.toString()) } } } }
viewModel.et.observe(this@MainActivity, Observer { uitv.text = it if (uiet.text.toString() != it) { uiet.setText(it) } }) }
|
写到扩展函数中就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| verticalLayout { editText { bindText(viewModel.et, this@xxxActivity) }
uitv = textView { bindText(viewModel.et, this@xxxActivity) }
}
fun TextView.bindText(field: MutableLiveData<String>, lo: LifecycleOwner) { field.observe(lo, Observer { if (text.toString() != it) { text = it } })
textChangedListener { afterTextChanged { if (field.value != it.toString()) { field.postValue(it.toString()) } } } }
|
欠缺思考
- [ ] 写死了数据的类型为
String
,想写成泛型
- [ ] 写死了更新动作为
setText
,想通过参数指定,setText对应的是String,所以需要在真正用到更多类型的时候再考虑
- [ ] 不能在扩展函数中直接获得LifecycleOwner,总是需要参数传递…
- 在anko layout 组件下直接使用
context
获得的并非activity而是 androidx.appcompat.view.ContextThemeWrapper
参考
- 基于xml的数据绑定方法
- 使用了泛型的例子