传统的MVC模式中,通过 findViewById() 初始化UI控件,当页面中的控件比较多时,就得进行重复而繁琐的初始化以及控件变量的管理,这时候就可以使用视图绑定来管理UI控件,省去这些重复的代码,而数据绑定则能够简化业务代码,实现声明式的UI。
使用数据绑定,需要在 build.gradle 中添加如下配置:
plugins {
...
id 'kotlin-kapt'
}
android {
...
dataBinding {
enabled = true
}
}
本文提供的例子使用的依赖如下;
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.activity:activity-ktx:1.7.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}
布局文件也有一些要求,得用 layout 作为根元素,layout 元素只能包含一个视图元素外加一个可选的数据元素,所以可以把原先的根布局放到 layout 元素中,或者新建布局文件时以 layout 作为根元素,这样数据绑定工具会帮助生成一个绑定类(binding class)。新生成的绑定类默认以布局文件命名,再加个Binding后缀。不过,命名格式不是蛇形式命名(snake_case),而是驼峰式命名(CamelCase)。
activity_main.xml
然后,就是代码中使用数据绑定了,主要使用 DataBindingUtil 初始化ViewBinding,使用Observable + ViewModel + LiveData 实现数据绑定,代码如下:
MainActivity.kt
package site.feiyuliuxing.databindingtest
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.CompoundButton
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import site.feiyuliuxing.databindingtest.adapter.JListAdapter
import site.feiyuliuxing.databindingtest.databinding.ActivityMainBinding
class MainViewModel : ObservableViewModel() {
val isRefreshing = MutableLiveData(false)
val text = MutableLiveData("123456")
}
val mainHandler = Handler(Looper.getMainLooper())
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels()
private val adapter = JListAdapter()
private val stopRunnable = Runnable {
adapter.updateItems((1..100).map { "item ${(Math.random() * 256).toInt()}" })
viewModel.isRefreshing.value = false
}
fun startRefresh() {
println(viewModel.text.value)
viewModel.isRefreshing.value = true
mainHandler.postDelayed(stopRunnable, 3000)
}
private fun stopRefresh() {
mainHandler.removeCallbacks(stopRunnable)
viewModel.isRefreshing.value = false
}
fun onRefreshCheckedChange(checkBox: CompoundButton, isChecked: Boolean) {
if(isChecked) {
startRefresh()
} else {
stopRefresh()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.recyclerView.adapter = adapter
binding.viewModel = viewModel
binding.activity = this
binding.lifecycleOwner = this
}
fun update(view: View) {
val isRefreshing = viewModel.isRefreshing.value ?: false
if(isRefreshing) {
stopRefresh()
} else {
startRefresh()
}
}
}
ObservableViewModel.kt
package site.feiyuliuxing.databindingtest
import androidx.databinding.Observable
import androidx.databinding.PropertyChangeRegistry
import androidx.lifecycle.ViewModel
open class ObservableViewModel: ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
callbacks.remove(callback)
}
/**
* Notifies listeners that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with [Bindable] to generate a field in
* `BR` to be used as `fieldId`.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
JListAdapter.kt
package site.feiyuliuxing.databindingtest.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import androidx.recyclerview.widget.RecyclerView
import site.feiyuliuxing.databindingtest.databinding.ListItemBinding
class JListAdapter : RecyclerView.Adapter() {
private val items = ArrayList()
fun updateItems(items: List) {
this.items.clear()
this.items.addAll(items)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JViewHolder {
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context))
val width = parent.context.resources.displayMetrics.widthPixels
binding.root.layoutParams = ViewGroup.LayoutParams(width, LayoutParams.WRAP_CONTENT)
return JViewHolder(binding)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: JViewHolder, position: Int) {
holder.bind(items[position])
}
class JViewHolder(private val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: String) {
binding.tvItemTitle.text = item
}
}
}
list_item.xml
总结:数据绑定基本的使用方式就是 Observable + ViewModel + LiveData,布局文件中声明变量,使用@{} 绑定数据,双向绑定使用@={},代码中初始化 ViewBinding 的时候给布局文件中声明的变量赋值并设置 lifecycleOwner,更多详细的内容参考官方文档《数据绑定使用入门》