Android Jetpack成員之一:DataBinding

本篇文章將從以下幾點介紹 DataBinding

  1. 環境配置
  2. Activity 中使用 DataBinding
  3. Fragment 中使用 DataBinding
  4. RecyclerView 中使用 DataBinding
  5. XML 中使用表達式
  6. XML 中直接綁定方法
  7. 結合可觀察變量和可觀察對象使用 DataBinding

正文開始,真香警告!(文章較長,建議準備一包瓜子...)

環境配置

使用 DataBinding 的前提需要完成以下兩步:

  1. 在工程的 build.gradle 下開啓 DataBinding 選項:

    android {
        // 注意dataBinding的大小寫啊!!!
        dataBinding {
            enabled = true
        }
    }
    
  2. 在工程的 gradle.properties 下啓用新的數據綁定編譯器:

    android.databinding.enableV2=true
    

Activity 中使用 DataBinding

環境配置好了之後,我們就可以使用 DataBinding 了,首先就拿 Activity 開刀吧,畢竟是最熟悉的陌生人 ^ - ^
1)佈局

<?xml version="1.0" encoding="utf-8"?>

<layout>
    <data>
        // UserBean爲數據類
        <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
        // otherName僅僅是一個String對象
        <variable name="otherName" type="String"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".data.binding.DataBindingActivity">

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:id="@+id/tv_first"
                  android:text="@{userBean.firstName}"
                  android:textSize="20sp"
                  android:gravity="center"
                  app:layout_constraintTop_toTopOf="parent"
                  app:layout_constraintLeft_toLeftOf="parent"/>

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:id="@+id/tv_last"
                  android:text="@{userBean.lastName + otherName}"
                  android:textSize="20sp"
                  android:gravity="center"
                  app:layout_constraintLeft_toLeftOf="parent"
                  app:layout_constraintTop_toBottomOf="@id/tv_first"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

看完上面的佈局文件,發現比平時寫佈局文件多了一些東西,我們來一個一個觀察:

  1. XML 最外層必須由 layout 標籤包圍;
  2. 你需要使用什麼樣的數據,可以在 data 標籤內通過 variable 來導入;
  3. 使用數據時,只需要通過 variablename 或者 name 的屬性來獲取內容即可。

Tips:進行完這一步之後,一定要 make 一下工程,否則下一步無法進行

2)綁定佈局
make 工程之後,會自動爲我們生成一個類,類名的規則是佈局文件名 + Binding,比如佈局文件名爲:activity_main,那麼生成的類名爲:ActivityMainBinding,這個類我們接下來會使用到,是綁定佈局的關鍵之處。

class DataBindingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 綁定佈局從setContentView()變成了DataBindingUtil.setContentView()
        val binding: ActivityDataBindingBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_data_binding
        )
        binding.userBean = UserBean("Activity", "Taonce")
        binding.otherName = " Android"
    }
}

佈局的綁定只是從 setContentView( laytouId ) 換成了 DataBindingUtil.setContentView( activity, layoutId),而且這個方法會返回一個 ViewDataBinding 對象,這裏的 ActivityDataBindingBindingViewDataBinding 子類,我們可以通過 ViewDataBinding 設置佈局所需要的數據。比如這裏想要設置 TextViewtext 屬性,不用再 setText( text ) 了,只需要設置 binding.userBeanbinding.otherName 就好了。

Fragment 中使用 DataBinding

知道了在 Activity 中如何使用 DataBinding 之後,Fragment 也是類似, 至少 XML 是不變的,我們只需要知道如何綁定就ok了:

class DataBindingFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Fragment中使用的是DataBindingUtil.inflate()來獲取ViewDataBinding對象
        val binding = DataBindingUtil.inflate<FragmentDataBindingBinding>(
            inflater,
            R.layout.fragment_data_binding,
            container, false
        )
        binding.userBean = UserBean("Fragment", "")
        return binding.root
    }
}

Fragment 中通過 DataBindingUtil.inflate() 來獲取 ViewDataBinding 對象,然後返回 ViewDataBinding.root,整個綁定就結束了!

Adapter 中使用 DataBinding

RecyclerView 中使用 DataBinding 稍微比上面兩種要複雜一點點,但是聰明的你,肯定是掃一眼就懂了。

1)RecyclerViewAdapter 綁定:在 XML 中設置 adapter 屬性就完事了~

// XML
<layout>

    <data>
        <variable name="adapter" type="com.taonce.myjetpack.data.binding.list.ListAdapter"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".data.binding.list.ListActivity">

        <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/rcv"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:adapter="@{adapter}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

// Activity
binding.adapter = ListAdapter(
    this, listOf(
        UserBean("one", ""),
        UserBean("two", ""),
        UserBean("three", "")
    )
)

2)Item 的數據綁定:
生成 Item 佈局文件:

<layout>

    <data>
        <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
    </data>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content">

        <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
                  android:id="@+id/tv_item"
                  android:textSize="20sp"
                  android:layout_gravity="center"
                  android:text="@{userBean.firstName}"
                  android:gravity="center"/>

    </LinearLayout>
</layout>

接下來需要在 Adapter 中將 Item 所需要的數據傳進來:

class ListAdapter(
    private val context: Context,
    val data: List<UserBean>
) : RecyclerView.Adapter<ListAdapter.Holder>() {
    // 綁定數據需要
    lateinit var binding: ItemBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        // 獲取ViewDataBinding對象
        binding = DataBindingUtil.inflate(
            LayoutInflater.from(context),
            R.layout.item, parent, false
        )
        return Holder(binding.root)
    }

    override fun getItemCount(): Int {
        return data.size
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        // 根據position將數據和xml中的userBean綁定
        binding.userBean = data[position]
        // 立即改變數據
        binding.executePendingBindings()
    }

    class Holder(val view: View) : RecyclerView.ViewHolder(view)
}

其實上面的註釋已經說明了一切,就是把 Holder 需要做個工作給省去了,通過 ViewDataBinding 對象進行數據綁定,不需要再手動的去 holder.tv.setText( text ) 了。

DataBinding 中使用表達式

上面的案例一直都是 XML 中簡單的綁定 text 屬性,試想一下如果我們傳入的值爲空,那麼豈不是很尷尬,所以 DataBinding 還給我們提供了多種表達式,以便更加靈活使用數據綁定,跟着例子一起來看看:

<?xml version="1.0" encoding="utf-8"?>

<layout>

    <data>
        <variable name="userBean" type="com.taonce.myjetpack.data.binding.UserBean"/>
        <variable name="age" type="Integer"/>
        <variable name="view" type="android.view.View"/>
        <variable name="ageLess20" type="String"/>
        <variable name="ageThan20" type="String"/>
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".data.binding.expression.ExpressionActivity">

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:textSize="20sp"
                  android:textColor="@color/colorAccent"
                  android:padding="10dp"
                  android:id="@+id/tv_one"
                  app:layout_constraintLeft_toLeftOf="parent"
                  app:layout_constraintTop_toTopOf="parent"
                  android:text="@{userBean.firstName ?? userBean.lastName}"/>

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:textSize="20sp"
                  android:textColor="@color/colorAccent"
                  android:padding="10dp"
                  android:id="@+id/tv_two"
                  app:layout_constraintTop_toBottomOf="@id/tv_one"
                  android:text='@{userBean.lastName.isEmpty() ? "lastName is null" : userBean.lastName}'/>

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:textSize="20sp"
                  android:textColor="@color/colorAccent"
                  android:padding="10dp"
                  android:id="@+id/tv_three"
                  app:layout_constraintTop_toBottomOf="@id/tv_two"
                  android:text="@{ageThan20}"
                  android:visibility="@{age > 20 ? view.VISIBLE : view.GONE}"/>

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:textSize="20sp"
                  android:textColor="@color/colorAccent"
                  android:padding="10dp"
                  android:id="@+id/tv_four"
                  app:layout_constraintTop_toBottomOf="@id/tv_two"
                  android:text="@{ageLess20}"
                  android:visibility="@{age &lt; 20 ? view.VISIBLE : view.GONE}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

上面的例子中,我們看到了幾個新鮮的符號,如:

  1. userBean.firstName ?? userBean.lastName:表示 firstName 不爲空時選擇 firstName,爲空時則選擇 lastName
  2. userBean.lastName.isEmpty() ? "lastName is null" : userBean.lastName:這個就是完完整整的一個三元操作符,相信大家在日常開發中都已經熟練使用過了;
  3. age > 20 ? view.VISIBLE : view.GONE:這是判斷的表達式,age 大於20的時候 View 顯示,否則隱藏;
  4. age &lt; 20 ? view.VISIBLE : view.GONE:咋一看,感覺好像沒見過 &lt;,其實它是就是 < 符號,不過要求強制寫成這樣罷了。

除了上面例舉的幾個以外,還提供了許多操作符,下面一一列出來:

  1. 數學表達式:+ - / * %
  2. 字符串表達式:+
  3. 邏輯表達式:&& ||
  4. 二進制表達式:& | ^
  5. 一元表達式:+ - ! ~
  6. 位移表達式:>> >>> <<
  7. 比較表達式:== > < >= <= (其中 < 需要寫成 &lt;)
  8. instanceof
  9. ()
  10. 文字類型:character, String, numeric, null
  11. 強轉:Cast
  12. 方法回調
  13. 屬性引用
  14. 數組
  15. 三元表達式:? :

具體的大家可以去 Android 官網上面去看,上面還有詳細的案例。(狗頭提醒:需要藉助 tizi)

DataBinding 綁定方法

方法的綁定分爲兩種,一種是帶參數的方法,另外一種是不帶參數的方法。其實處理起來都是很類似的,方法帶參數的話,我們可以通過 variable 來導入參數,然後在 Activity 中賦值就行了。在看下面的例子之前,我們首先要知道,綁定方法的形式都是以 lambda 來實現的。

先看 XML 文件:

<?xml version="1.0" encoding="utf-8"?>

<layout>

    <data>
        <!--方法所屬類,方法的定義不一定非要在Activity中,可以在任何類中,只需要再次定義就好-->
        <variable name="activity" type="com.taonce.myjetpack.data.binding.function.FunctionActivity"/>
        <!--通過variable把方法的參數傳遞過來-->
        <variable name="isToast" type="Boolean"/>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".data.binding.function.FunctionActivity">

        <Button android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/bt_one"
                app:layout_constraintTop_toTopOf="parent"
                android:text="不帶參數的方法"
                android:textAllCaps="false"
                android:onClick="@{() -> activity.buttonClick()}"/>

        <Button android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/bt_two"
                app:layout_constraintTop_toBottomOf="@id/bt_one"
                android:text="帶參數的方法"
                android:textAllCaps="false"
                android:onClick="@{() -> activity.toast(isToast)}"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

再來看看具體的方法:

class FunctionActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityFunctionBinding>(
            this, R.layout.activity_function
        )
        // 綁定xml中兩個變量
        binding.activity = this
        binding.isToast = true
    }

    /*
    * 不帶參數的方法
    * */
    fun buttonClick() {
        Toast.makeText(this, "button click", Toast.LENGTH_SHORT).show()
    }

    /*
    * 帶參數的方法
    * */
    fun toast(isToast: Boolean) {
        if (isToast) {
            Toast.makeText(this, "isToast is true", Toast.LENGTH_SHORT).show()
        }
    }
}

結合可觀察字段和可觀察對象

可觀察字段或可觀察對象的意思就是,當這些字段或對象的值發生改變時,與之相關聯的 UI 將會自動更新。Android 提供了以下幾種可觀察類:

  • ObservableBoolean:布爾類型
  • ObservableByte:字節類型
  • ObservableChar:字符類型
  • ObservableShort:短整型
  • ObservableInt:整型
  • ObservableLong:長整型
  • ObservableFloat:單精度浮點型
  • ObservableDouble:雙精度浮點型
  • ObservableParcelable:序列化類型

有人說,爲什麼沒有 String 類型的,不存在的,String 雖然不是基本數據類型,但是還是得提供的,畢竟它的上場率可是登頂的,要想使用 String 需要藉助 ObservableField 對象,一會來看下如何使用。說了這麼多類型不可能一一舉例了,在這就以 StringInt 爲例說明下如何使用這些可觀察字段吧。

XML

<?xml version="1.0" encoding="utf-8"?>

<layout>
    <data>
        <import type="androidx.databinding.ObservableField"/>
        <!--因爲<需要轉義成&lt;-->
        <variable name="title" type="ObservableField&lt;String>"/>
        <variable name="activity" type="com.taonce.myjetpack.data.binding.observable.ObservableActivity"/>

    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".data.binding.observable.ObservableActivity">

        <TextView android:layout_width="match_parent" android:layout_height="wrap_content"
                  app:layout_constraintTop_toTopOf="parent"
                  android:id="@+id/tv_title"
                  android:text="@{title ?? title}"
                  android:textSize="20sp"
                  android:padding="10dp"
                  android:gravity="center"/>

        <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
                android:id="@+id/bt_observable"
                android:onClick="@{()->activity.changeContent()}"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                android:text="修改界面的值"
                android:textSize="20sp"
                android:padding="10dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Activity

class ObservableActivity : AppCompatActivity() {
    lateinit var binding: ActivityObservableBinding
    private val title = ObservableField("ObservableActivity")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_observable)
        binding.activity = this
        binding.title = title
    }

    fun changeContent() {
        // 可觀察字段通過set()方法更新值
        title.set("ChangeTitle")
    }
}

XML 文件中綁定可觀察字段後,在 Activity 通過該字段的 set( value ) 方法來更新值並且通知界面更新 UI。

到這裏有人會提出疑問,假如我綁定的不是上面的字段,而且某個數據類咋辦,難道還是要通過手動去更新 UI ?不不不,官網也提供了方法可將數據類的變量轉化爲可觀察對象,只要值改變,也會自動的更新 UI,一起看看吧。

class ObservableBean : BaseObservable() {

    // 必須使用 @get:Bindable 註解
    @get:Bindable
    var name: String = ""
        set(value) {
            field = value
            // 每當值set()後,通過notifyPropertyChanged()方法去指定更新
            // 可更新某個值,可以更新整個數據,取決於你BR後面的屬性
            // BR.name 就代表只更新name相關的UI
            // BR._all 可更新所有的BR中字段相關聯的UI
            notifyPropertyChanged(BR.name)
        }

    @get:Bindable
    var age: Int = -1
        set(value) {
            field = value
            notifyPropertyChanged(BR.age)
        }
}

代碼中註釋解釋了一部分,這裏再加上兩點:

  1. 在 Kotlin 中使用註解的話,必須在工程的 build.gradle 中加上 apply plugin: 'kotlin-kapt'
  2. 每當在類中聲明瞭一個加了註解的變量,都需要 make 一下工程,這樣纔會在 BR 類中生成對應的變量標誌,不然系統找不到對應的變量

創建完成之後,就可以像之前綁定數據類一樣操作了,跟之前的操作毫無區別,這裏就不再過多解釋了。

推薦閱讀:
KTX - 更簡潔的開發Android

如果本文章你發現有不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章