介绍
这篇文章介绍接入viewModel的使得app更加解耦,实现方式有两种:ViewModel+Observable和ViewModel+LiveData,默认的是ViewModel+Observable
布局文件
<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.android.databinding.basicsample.data.ProfileObservableViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- A simple binding between a TextView and a string observable in the ViewModel -->
<TextView
android:id="@+id/name"
android:text="@{viewmodel.name}"
.../>
<!-- A simple binding between a TextView and a string observable in the ViewModel -->
<TextView
android:id="@+id/lastname"
...
android:text="@{viewmodel.lastName}"
.../>
<!-- A custom Binding Adapter (`app:popularityIcon`) is used passing `viewmodel.popularity`
as a parameter. Because it's a @Bindable property, the ImageView is automatically updated.
-->
<ImageView
android:id="@+id/imageView"
...
app:popularityIcon="@{viewmodel.popularity}"/>
<!-- Conversions like Integer to String are expressions simple enough for the layout. An
alternative would be to create a getter in the ViewModel to expose likes as Strings. -->
<TextView
android:id="@+id/likes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.toString(viewmodel.likes)}"
.../>
<!-- Listeners can accept lambdas so in this case the ViewModel deals with the event,
bypassing the activity. -->
<Button
android:id="@+id/like_button"
android:onClick="@{() -> viewmodel.onLike()}"
.../>
...
<!-- android:progressTint is only available in API 21+ so we deal with that in the
Binding Adapter. -->
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:max="@{100}"
app:hideIfZero="@{viewmodel.likes}"
app:progressTint="@{viewmodel.popularity}"
app:progressScaled="@{viewmodel.likes}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/like_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="@+id/like_button"
app:layout_constraintTop_toBottomOf="@+id/like_button"
tools:progressBackgroundTint="@android:color/darker_gray"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
1.name和lastName以及Like的文本处理是基本的binding方式
2.imageView的app:popularityIcon是通过BindAdapter实现的自定义布局属性
@BindingAdapter("app:popularityIcon")
@JvmStatic fun popularityIcon(view: ImageView, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))
view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
这个静态方法定义了app:popularityIcon属性,接收一个作用对象ImageView,属性值是Popularity,方法体是根据Popularity的类型得到Tint颜色值和Drawable。
private fun getAssociatedColor(popularity: Popularity, context: Context): Int {
return when (popularity) {
Popularity.NORMAL -> context.theme.obtainStyledAttributes(
intArrayOf(android.R.attr.colorForeground)).getColor(0, 0x000000)
Popularity.POPULAR -> ContextCompat.getColor(context, R.color.popular)
Popularity.STAR -> ContextCompat.getColor(context, R.color.star)
}
}
private fun getDrawablePopularity(popularity: Popularity, context: Context): Drawable? {
return when (popularity) {
Popularity.NORMAL -> {
ContextCompat.getDrawable(context, R.drawable.ic_person_black_96dp)
}
Popularity.POPULAR -> {
ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp)
}
Popularity.STAR -> {
ContextCompat.getDrawable(context, R.drawable.ic_whatshot_black_96dp)
}
}
}
这里Popularity是一个枚举类,when表示的条件全部覆盖Popularity的类型,所以when中不需要else语句。
enum class Popularity {
NORMAL,
POPULAR,
STAR
}
3.like_button的点击事件接收一个lambda表达式,调用viewmodel.onLike()
4.progressBar的app:hideIfZero和app:progressTint也是两个自定义属性
@BindingAdapter("app:hideIfZero")
@JvmStatic fun hideIfZero(view: View, number: Int) {
view.visibility = if (number == 0) View.GONE else View.VISIBLE
}
@BindingAdapter("app:progressTint")
@JvmStatic fun tintPopularity(view: ProgressBar, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.progressTintList = ColorStateList.valueOf(color)
}
}
ViewModel
class ProfileObservableViewModel : ObservableViewModel() {
val name = ObservableField("Ada")
val lastName = ObservableField("Lovelace")
val likes = ObservableInt(0)
fun onLike() {
likes.increment()
// You control when the @Bindable properties are updated using `notifyPropertyChanged()`.
notifyPropertyChanged(BR.popularity)
}
@Bindable
fun getPopularity(): Popularity {
return likes.get().let {
when {
it > 9 -> Popularity.STAR
it > 4 -> Popularity.POPULAR
else -> Popularity.NORMAL
}
}
}
}
1.定义了三个可观察对象ObservableField("Ada"),ObservableField("Lovelace"),ObservableInt(0)
2.通过 @Bindable 注解添加get方法,这样布局文件中就可以引用popularity属性(@{viewmodel.popularity}
)
3.onLick函数修改likes的值,然后调用 notifyPropertyChanged(BR.popularity)更新视图中的popularity属性
注意这里的likes.increment()是一个扩展函数
private fun ObservableInt.increment() {
set(get() + 1)
}
ViewModelActivity.kt
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProvider(this).get(ProfileObservableViewModel::class.java)
// Obtain binding
val binding: ViewmodelProfileBinding =
DataBindingUtil.setContentView(this, R.layout.viewmodel_profile)
// Bind layout with ViewModel
binding.viewmodel = viewModel
}
}
Activity中的逻辑很简单,就是将viewModel和Activity绑定到一起,viewModel可以感知Activity的生命周期,这样就避免了内存泄漏的风险。
这种MVVM模式使得model和Activity完全解耦,ViewModel中只处理业务逻辑,Databinding使得view和model相互响应。其中BindeAdapter负责将POJO转为VO,并且显示到View上。
ViewModel+LiveData 模式
ViewModel中使用LiveData替换BaseObserver实现Model的更改自动刷新ui
class ProfileLiveDataViewModel : ViewModel() {
private val _name = MutableLiveData("Ada")
private val _lastName = MutableLiveData("Lovelace")
private val _likes = MutableLiveData(0)
val name: LiveData<String> = _name
val lastName: LiveData<String> = _lastName
val likes: LiveData<Int> = _likes
// popularity 作为LiveData暴漏出去
// 使用Transformation替换@Bindable属性
// popularity is exposed as LiveData using a Transformation instead of a @Bindable property.
val popularity: LiveData<Popularity> = Transformations.map(_likes) {
when {
it > 9 -> Popularity.STAR
it > 4 -> Popularity.POPULAR
else -> Popularity.NORMAL
}
}
fun onLike() {
_likes.value = (_likes.value ?: 0) + 1
}
}
使用LiveData的时候,需要在Activity或者fragment中设置LifecycleOwner
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// LiveData needs the lifecycle owner
binding.lifecycleOwner = this
}
}
LiveData和Observable Fields 相比的优势是
LiveData支持Transformations
LiveData跟很多的组件库都兼容,比如Room,WorkManager
这个页面的控制progressBar的隐藏和显示是通过BindAdapter自定义属性的方式。
上篇文章是通过@BindingConversion将接收的boolean转为visibility的值。
这种转换是不安全的因为BindingConversion在app中是不受控制的,他会把任何属性接收到的boolean都转为View的visibility integers
网友评论