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開發相關博客更新!

相關文章
相關標籤/搜索