MVVM 中的Databinding

引子

Android 中的MVVM模式的实现其很大一部分依托于Android Architecture Component 中的Databinding的实现,DataBinding让咱们的数据和界面产生了链接,而不须要咱们手动的操做着使人烦闷的控件赋值操做。MVVM也正是借助于DataBinding实现了数据与界面的解耦。其相似与一种轻量级的标记语言,经过ide的支持,实现界面支持基础的标记语法操做,并经过编译布局文件将对应的语法转换为多个与之绑定的实现类来负责Model与数据之间的绑定,因而可知其发展方向相似与前端,知道前端的同窗应该会发现这很相似与java中的jsp、python的模板语言,但就目前而言,其功能相对较弱,并不能如前面两种标记语言那般强大到能够直接实现布局与代码的混合开发,但将来可期。前端

databinding

DataBinding

DataBinding的使用Google给了很多的示例,写的也挺全面,除了都是英文外没什么缺点,基本上DataBinding的各类使用方式都有对应的操做,相较于网上充斥的介绍文档,我的比较推荐看google提供的demo案例,看的仔细了你会有一种 麻雀虽小、五脏俱全 的感受,让你感受Google大爷仍是你家大爷。这里给出了Goolge提供相关的DataBinding操做的示例导航以及使用的简要介绍.java

DataBindng 案例

这里给出了Google的有关DataBiding的示例最后一个是我用的MVVM模式的案例(MVVM集数成了DataBinding),DataBidng单独使用的话其优点并非很大,通常而言DataBinding都是配合着观察者使用的,以下的案例大多都是使用Observer数据或者LiveData数据配合使用。python

DataBinding的绑定

DataBidng 的绑定主要分为:变量绑定、事件绑定和适配器绑定,其绑定方式有分为单向绑定和双向绑定,其实现也都稍有不一样。android

变量绑定

变量的绑定是经过实现传入的绑定对象,经过绑定的对象的参数进行绑定,同时也支持表达式、方法输出等,以下示例:git

<lanyout 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> <import type="com.example.android.databinding.basicsample.R"/>
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
       <variable name="user" type="com.example.android.databinding.basicsample.data.ObservableFieldProfile" />
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">

      <!--user对象的变量实现 -->
      <TextView android:id="@+id/name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="128dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@{user.name}" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/name_label"/>


    <!--表达式实现 -->
      <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="24dp" android:layout_marginTop="24dp" android:contentDescription="@string/profile_avatar_cd" android:minHeight="48dp" android:minWidth="48dp" app:layout_constraintTop_toBottomOf="@+id/name" app:layout_constraintStart_toStartOf="parent" android:tint="@{user.likes > 9 ? @color/star : @android:color/black}" app:srcCompat="@{user.likes < 4 ? R.drawable.ic_person_black_96dp : R.drawable.ic_whatshot_black_96dp }"/>

      <!--静态方法实现 -->
      <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}" android:visibility="@{ConverterUtil.isZero(user.likes)}" app:progressScaled="@{user.likes}" app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintStart_toStartOf="parent" tools:progressBackgroundTint="@android:color/darker_gray"/>

  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

复制代码
事件绑定

实现的绑定主要指的是layout中提供的方法传入实现(基本就是代指onClick方法,固然也可自行进行定制),事件的绑定方式有两种:一种是lambda方式、另外一种是保证和andorid实现方法参数相同的只须要传入方法名,以下:github

/** *带绑定viewmodle */
class ProfileLiveDataViewModel : ViewModel() {
        fun onLike() {
           _likes.value = (_likes.value ?: 0) + 1
        }

        fun disLike(view:View) {
           _unlikes.value = (_unlikes.value ?: 0) + 1
        }

}

复制代码
<lanyout 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> <import type="com.example.android.databinding.basicsample.R"/>
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
      <variable name="viewmodel" type="com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel"/>
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">

      <!--lambda方式实现 -->
      <Button android:id="@+id/like_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginTop="16dp" android:onClick="@{() -> viewmodel.onLike()}" android:text="@string/like" app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="@+id/imageView" app:layout_constraintTop_toBottomOf="@+id/likes"/>


       <!--原生样式实现 -->
        <Button android:id="@+id/unlike_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginTop="16dp" android:onClick="@{viewmodel.disLike}" android:text="@string/like" app:layout_constraintTop_toBottomOf="@id/like_button" app:layout_constraintStart_toStartOf="@+id/imageView" app:layout_constraintTop_toBottomOf="@+id/likes"/>


  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

复制代码
适配器绑定

提及DataBinding的适配器绑定就有点高级了,它是一个相似kotlin的扩展同样的东西,只不过kotlin的扩展做用的是实现代码的具体类中,而DataBinding的适配器做用到的是布局文件中的view的属性中,它能够帮咱们减小不少麻烦的操做,让咱们的代码看起来更具备可读性、美观性,以下:app

/** * Databinding适配器的使用 * 须要注意的是因为适配器使用的是java的static方法,为了适配kotlin这里每一个适配器方法均须要添加注解@JvmStatic使得其能够与java适配 * 详细请参考kotlin官网静态的使用 * * */
object BindingAdapters {
    /** * * 一个绑定的适配器能够在任何地方陪使用用于设置ImageView的Popularity,接受值为popularity * */
    @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))
    }
  }

复制代码
<lanyout 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> <import type="com.example.android.databinding.basicsample.R"/>
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
      <variable name="viewmodel" type="com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel"/>
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent">

      <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="24dp" android:layout_marginTop="24dp" android:contentDescription="@string/profile_avatar_cd" android:minHeight="48dp" android:minWidth="48dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:popularityIcon="@{viewmodel.popularity}"/>


  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

复制代码
双向绑定

DataBinding其自己并不支持双向绑定,通常都是经过一个Observer类型数据进行观察实现的双向绑定的过程,通常实现的方式分为两种:一种是Observabler接口的实现,另外一种则是LiveData的绑定实现.这里有几个小的地方须要注意点:xml中通常使用 @{} 用于给控件赋值,xml中通常使用 **=@{}**实现xml控件的赋值和控件值变化修改对应变量的值。jsp

  • Observer接口的实现

Observer接口实现原理说来也简单,但操做并不简单,它是DataBinding内部封装的一个接口,代理实现属性的变化自动更新ui界面,但代码中变量的变化须要咱们主动触发通知UI界面进行更新,其实现以下:mvvm

/** *做为实现Observer功能的一个viewmodle基类 */
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)
    }

    /** * 这个方法调用会对model下的全部属性进行检查并更新ui */
    fun notifyChange() {
        callbacks.notifyCallbacks(this, 0, null)
    }

    /** * * * 更新modle下制定id的控件值,其中id是databinding对应生成的一个变量id,不一样于控件的属性id,databinding其本质是控件与属性的一一绑定. * * @param fieldId The generated BR id for the Bindable field. */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)
    }
}




/** * viewmodle的实现类,具体须要绑定的viewmodle,主要对于须要双向绑定的须要手动提醒更新 */
class ProfileObservableViewModel : ObservableViewModel() {
    val name = ObservableField("Ada")
    val lastName = ObservableField("Lovelace")
    val likes =  ObservableInt(0)

    fun onLike() {
        likes.increment()
        //须要手动提醒更新
        notifyPropertyChanged(BR.popularity)
    }

    @Bindable
    fun getPopularity(): Popularity {
        return likes.get().let {
            when {
                it > 9 -> Popularity.STAR
                it > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        }
    }
}

enum class Popularity {
    NORMAL,
    POPULAR,
    STAR
}

private fun ObservableInt.increment() {
    set(get() + 1)
}

复制代码
  • LiveData绑定实现

LiveData的实现相对就比较容易写,LiveData其本省就是被做为一个Observer监听模式的一个存在,以下:ide

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 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实现双向绑定,并且LiveData自己还有其余好玩的方式等待咱们的探索。本篇介绍的代码相似与伪代码性质,不具备贯通性,具体实现的使用我的仍是比较推荐你们能够去看看我上面推荐的DataBinding的demo示例代码,那里有你想要的一切,该有的它都有!另外,须要注意的是,在咱们须要的model里添加设置代码:

android {

   .....
   dataBinding {
    enabled true
   }
 
}
复制代码

原文连接

欢迎关注个人我的博客Enjoytoday,有更新更全的python、Kotlin、Java、Gradle开发相关博客更新!

相关文章
相关标签/搜索