Databinding:
让布局文件承担了部分原本属于页面的工作, 使页面与布局耦合度进一步降低
androidx.databinding
简单记录Kotlin 下使用MVVM的例子。
实现:模拟数据变化, 更新UI (两个TextView, 一个显示名字,一个显示计数)
一、app 目录build.grade 启动 dataBinding
更新 2021/7/8 android gradle plugin 4.0.0 以上使用 buildFeatures,
android {
//...
buildFeatures {
dataBinding = true
// for view binding:
// viewBinding = true
}
}
以下方式为已抛弃
// dataBinding {
// enabled true
// }
参考:
二、创建UserData (M) 和UserViewModel
class UserViewModel : BaseObservable {
private var userData = UserData(this)
constructor()
//e: ����: Bindable must be on a member in an Observable class. UserViewModel is not Observable
@Bindable
fun getUserName(): String? {
return userData.getCurUserName()
}
@Bindable
fun getCount(): String {
return userData.getCurrCount().toString()
}
fun changeUserName() {
notifyPropertyChanged(BR.userName)
notifyPropertyChanged(BR.count)
}
}
1. UserViewModel: 主要提供两个Bindable 函数, 用于布局文件(V- view);
changeUserName() 通知UI 数据已经变化,请更新UI
需要注意: Bindable 修饰函数(实际上为变量的get 方法), 需要所在的ViewModel 继承 BaseObservable,
否则,会报错:
Bindable must be on a member in an Observable class. UserViewModel is not Observable
此外,也可以通过定义Observable 的变量,实现当数据变化时,自动更新UI, 则不需要手动调用notifyPropertyChanged
var userName = ObservableField<String>()
userName.set(null)
userName.set("Xiaoming")
创建UserData:
class UserData {
private var userNameList = mutableListOf("ZhangSan", "LiSi", "WangWu"
, "ZhaoLiu", "YangBa", "SunJiu")
private var curUserNameIndex = 0
private var userViewModel: UserViewModel
private var changeCount = 0
private val maxCount = 20
constructor(userViewModel: UserViewModel) {
this.userViewModel = userViewModel
//模拟数据变化
simulateDataChange()
}
fun getCurUserName() : String ?{
return userNameList[curUserNameIndex]
}
fun getCurrCount () : Int {
return changeCount
}
private fun simulateDataChange() {
Thread{
for ( count in 0 until maxCount) {
Thread.sleep(2000)
changeCount++
curUserNameIndex++
if (curUserNameIndex >= userNameList.size) {
curUserNameIndex = 0
}
this.userViewModel.changeUserName()
}
}.start()
}
}
1. UserData: 提供用户名字(username) 和 当前计数(changeCount), 启动线程,每隔一秒更新数据(即curUserNameIndex),
并且调用 ViewModel 的 changeUserName() 告知数据变化了
其中,UserData / UserViewModel 相互持有引用,实际上,UserData(Model) 不需要持有ViewModel
三、布局文件 (V)
<?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="userViewModel"
type="com.example.androidmvvmtest.UserViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/userNameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userViewModel.userName}"
android:textSize="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/changeCountText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{userViewModel.count}"
android:textSize="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/userNameTextView"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
其中,
1. 布局文件为 <layout > </layout> 标签
2. <data> </data> 标签定义引用的 ViewModel 类型和变量, 使用 variable 标签包裹,其中name 为变量名,type为类所在的包
<variable
name="userViewModel"
type="com.example.androidmvvmtest.UserViewModel" />
如果想要引用Android 自带VIEW 的API, 例如 View.VISIBLE, 只需import 这个类
<import
type="android.view.View" />
3. 使用到viewmodel的地方: android:text="@{userViewModel.userName}"
表示显示的文本,是引用 userViewModel 里面的 getUserName()的返回值 (省略了get)
四、 Activity 中调用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var dataBinding =
DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
var userViewModel = UserViewModel()
dataBinding.userViewModel = userViewModel
}
}
主要根据 DataBindingUtils 进行setContentView (取代了之前的setContentView(R.layout.activity_main),
并且返回一个布局文件对应的 Binding 类型, 作用是设置布局文件里面的 ViewModel 变量