美文网首页
Databinding-samples(四)-ViewModel

Databinding-samples(四)-ViewModel

作者: 烧伤的火柴 | 来源:发表于2020-04-28 14:32 被阅读0次

介绍

这篇文章介绍接入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

相关文章

网友评论

      本文标题:Databinding-samples(四)-ViewModel

      本文链接:https://www.haomeiwen.com/subject/sjyvwhtx.html