美文网首页
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