本篇文章將從以下幾點介紹 DataBinding
:
- 環境配置
- 在
Activity
中使用DataBinding
- 在
Fragment
中使用DataBinding
- 在
RecyclerView
中使用DataBinding
- 在
XML
中使用表達式 - 在
XML
中直接綁定方法 - 結合可觀察變量和可觀察對象使用
DataBinding
正文開始,真香警告!(文章較長,建議準備一包瓜子...)
環境配置
使用 DataBinding
的前提需要完成以下兩步:
-
在工程的
build.gradle
下開啓DataBinding
選項:android { // 注意dataBinding的大小寫啊!!! dataBinding { enabled = true } }
-
在工程的
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>
看完上面的佈局文件,發現比平時寫佈局文件多了一些東西,我們來一個一個觀察:
-
XML
最外層必須由layout
標籤包圍; - 你需要使用什麼樣的數據,可以在
data
標籤內通過variable
來導入; - 使用數據時,只需要通過
variable
的name
或者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
對象,這裏的 ActivityDataBindingBinding
是 ViewDataBinding
子類,我們可以通過 ViewDataBinding
設置佈局所需要的數據。比如這裏想要設置 TextView
的 text
屬性,不用再 setText( text )
了,只需要設置 binding.userBean
和 binding.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)RecyclerView
和 Adapter
綁定:在 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 < 20 ? view.VISIBLE : view.GONE}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
上面的例子中,我們看到了幾個新鮮的符號,如:
-
userBean.firstName ?? userBean.lastName
:表示firstName
不爲空時選擇firstName
,爲空時則選擇lastName
; -
userBean.lastName.isEmpty() ? "lastName is null" : userBean.lastName
:這個就是完完整整的一個三元操作符,相信大家在日常開發中都已經熟練使用過了; -
age > 20 ? view.VISIBLE : view.GONE
:這是判斷的表達式,age
大於20的時候View
顯示,否則隱藏; -
age < 20 ? view.VISIBLE : view.GONE
:咋一看,感覺好像沒見過<
,其實它是就是<
符號,不過要求強制寫成這樣罷了。
除了上面例舉的幾個以外,還提供了許多操作符,下面一一列出來:
- 數學表達式:
+ - / * %
- 字符串表達式:
+
- 邏輯表達式:
&& ||
- 二進制表達式:
& | ^
- 一元表達式:
+ - ! ~
- 位移表達式:
>> >>> <<
- 比較表達式:
== > < >= <=
(其中<
需要寫成<
) instanceof
()
- 文字類型:character, String, numeric,
null
- 強轉:
Cast
- 方法回調
- 屬性引用
- 數組
- 三元表達式:
? :
具體的大家可以去 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
對象,一會來看下如何使用。說了這麼多類型不可能一一舉例了,在這就以 String
和 Int
爲例說明下如何使用這些可觀察字段吧。
XML
:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<import type="androidx.databinding.ObservableField"/>
<!--因爲<需要轉義成<-->
<variable name="title" type="ObservableField<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)
}
}
代碼中註釋解釋了一部分,這裏再加上兩點:
- 在 Kotlin 中使用註解的話,必須在工程的
build.gradle
中加上apply plugin: 'kotlin-kapt'
- 每當在類中聲明瞭一個加了註解的變量,都需要
make
一下工程,這樣纔會在BR
類中生成對應的變量標誌,不然系統找不到對應的變量
創建完成之後,就可以像之前綁定數據類一樣操作了,跟之前的操作毫無區別,這裏就不再過多解釋了。
推薦閱讀:
KTX - 更簡潔的開發Android
如果本文章你發現有不正確或者不足之處,歡迎你在下方留言或者掃描下方的二維碼留言也可!