發(fā)現(xiàn)我好久沒寫博客,其實最近一直都很想寫博客的,但是不知道寫點什么好。剛好碰上最近在學(xué)習Android的MVVM設(shè)計模式以及官方提供給我們的控件,所以才有了這篇文章。(其實還是因為我懶,我懶!)
今天我想給大家講講DataBinding,為了保證我寫的不會出錯,我也借鑒參考了不少文章和視頻。給大家看看一篇我個人覺得還不錯的。DataBinding最全使用說明(掘金博客)還有某課網(wǎng)的視頻,Android Data Binding實戰(zhàn)-入門篇、Android Data Binding實戰(zhàn)-高級篇
Android MVVM探索系列
Android MVVM探索(一) - DataBiding初解
Android MVVM探索(二) - DataBiding常用注解
Android MVVM探索(三) - ViewModel,DataBinding,LiveData混合三打
1, 什么是DataBinding?
DataBinding,2015年IO大會介紹的一個框架,字面理解即為數(shù)據(jù)綁定,是Google對MVVM在Android上的一種實現(xiàn),可以直接綁定數(shù)據(jù)到xml中,并實現(xiàn)自動刷新(即,數(shù)據(jù)變化UI進行相應(yīng)的變化)。而且還支持一些表達式。比如常見的三元運算符:
1+x == 3 ? "true" : "false"
它還可以支持lambda表達式:
(v,fcs) -> presenter.onFocusChange(user)}
使用了DataBinding,可以省去一些控件綁定代碼,例如:findviewById等。
2, 開始使用DataBinding
要想使用DataBinding的話,首先要在你安卓工程中,安卓Application的module(一般為app這個module)的android配置中加上如下代碼:
android{
// 這里省去一些常有的配置代碼
dataBinding {
enabled = true;
}
}
另外,如果你是使用Kotlin進行編程的話,你還要在加入了上面代碼的Gradle文件中頂部加上以下代碼,否則Kotlin將無法識別DataBinding資源,至于什么是DataBinding資源,我們后面會提到:
apply plugin: 'kotlin-kapt'
是的,你沒有看錯,就這么簡單我們就加上了DataBiding,不需要引入任何依賴。
首先我們先建立一個普通類作為ViewModel:
package top.cyixlq.test
class MainViewModel {
var name = "張三"
var age = 15
var isMan = true
fun log() {
Log.d("MyTAG", "按鈕被點擊了一下")
}
}
其次,我們要將我們布局文件代碼進行一些改動。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="top.cyixlq.test.MainViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.name}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.age)}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.isMan ? @string/man : @string/woman}"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{v -> viewModel.log()}"
android:text="點我"/>
</LinearLayout>
</layout>
最后,改造我們的Activity代碼:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// 第一種將數(shù)據(jù)填充到xml文件中的方法(代碼在下面這行),我們直接實例化了一個MainViewModel賦值給BR資源中一個叫viewModel的變量
// binding.setVariable(BR.viewModel, MainViewModel())
// 以下是一些說明:
// BR就是前文提到的DataBinding資源,像R文件一樣自動生成,記錄所有xml中data標簽內(nèi)的變量名稱,有點像控件id的感覺
// viewModel來自布局文件中data標簽內(nèi)的variable標簽中的name
// 第二種將數(shù)據(jù)填充到xml文件中的方法(代碼在下面這行),viewModel這個變量名視你在xml中variable標簽中的name而定
binding.viewModel = MainViewModel()
// 假如你的name為user,并且class名稱也為User的話(name和class的名稱不一定要相同)
// 那么代碼就是binding.user = User()
// java 代碼如下
// binding.setViewModel(new MainViewModel())
// binding.setUser(new User())
}
override fun onDestroy() {
super.onDestroy()
// 在Activity銷毀時記得解綁,以免內(nèi)存泄漏
binding.unbind()
}
}
3, 加入DataBinding后,xml文件的一些新的用法
-
數(shù)據(jù)的填充
可以很明顯的看到,我們在布局文件的最外層不是任何布局標簽,而是layout標簽。之后再引入data標簽,data里面是變量集合,整個xml文件中只允許有一個data標簽。data標簽中可以包含多個variable。name代表變量名稱,type是變量類型。在activity中,我們新建了一個binding變量,并且通過binding變量把MainViewModel實例化的對象賦值到xml文件中,這樣我們在xml中就可以直接填充到對應(yīng)控件中。通過@{},我們的控件就可以直接引用到viewModel中的對應(yīng)的值。就像:
android:text="@{viewModel.name}" -
import標簽
就像java中的import關(guān)鍵字一樣,可以導(dǎo)入類型,所以我們上面的xml文件中data部份還可以這樣寫:
<import type="top.cyixlq.test.MainViewModel"/> <variable name="viewModel" type="MainViewModel"/>我們還注意到,填充age屬性的時候,我們是@{String.valueOf(viewModel.age)}。因為age是整數(shù)型,我們知道TextView的Text是不可以為整數(shù)型的,所以我們使用了String這個類中的方法進行了轉(zhuǎn)換。按理說,String理應(yīng)也需要使用import標簽進行引入,然而我們并沒有這么做。是的,和Java一樣,java.lang包下的東西是自動引入的。
-
三元運算符和lambda表達式以及簡單運算
我們可以看到,我們填充isMan這個屬性的時候使用了三元運算符,并且使用@string/man和@string/woman作為兩個可選值。看起來是不是很神奇?其實我們也可以直接這樣寫:
android:text='@{viewModel.isMan ? "男" : "女"}'但是值得注意的是,在Windows下,我們這樣寫可能會報錯。是關(guān)于utf-8的一個錯誤,具體不太清楚。如果你是java代碼,在編譯的時候會告訴你這個錯誤。如果是kotlin下,就會顯示無法打印這個錯誤log。所以我還是推薦引用string資源。
就像我們在xml文件中設(shè)置按鈕的點擊事件一樣,我們可以直接引入lambda表達式,從而直接調(diào)用viewModel中的公開方法,是不是覺得簡單多了?
雖然可以直接在xml文件中進行運算了,例如字符串的拼接,數(shù)字的加減,如下所示:
android:text="@{String.valueOf(viewModel.age + 1)}" android:text='"性別:" + viewModel.isMan ? "男" : "女"'但是,我不推薦在xml文件中進行過于復(fù)雜的運算,可以在ViewModel類中處理好之后利用函數(shù)返回。如下所示:
<!-- xml文件中 --> android:text="@{viewModel.convertSex()}" // MainViewModel文件中 fun convertSex(): String { var result = "性別:" val sex = if (isMan) "男" else "女" return result + sex } -
include標簽的一些變化
我們在開發(fā)中難免要進行布局的復(fù)用,這就會用到include標簽了,但是如果我們引入的布局文件中也有variable怎么辦,怎么才能從當前布局文件中傳入到include導(dǎo)入的布局中呢?請直接看代碼說明!我們以自己做一個標題欄為例。
- 首先隱藏我們原有的標題欄,在Activity的onCreate中加入下面的代碼:
supportActionBar?.hide() // java代碼 // ActionBar actionBar = getSupportActionBar(); // if (actionBar != null) actionBar.hide() - 新建一個layout_title_bar.xml,內(nèi)容如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="title" type="String"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="@color/colorPrimary"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/white" android:text="@{title}"/> </RelativeLayout> </layout> - 在activity_main.xml中加入以下代碼:
<variable name="text" type="String" /> <!-- 這里傳過去的屬性名稱要與include引入的布局文件中variable的name一樣 --> <!-- 同樣,這里可以使用MainViewModel中的某個字符串屬性作為值傳過去,不聲明一個新的variable --> <include layout="@layout/layout_titlt_bar" title="@{text}"/> - 別忘了在Activity中給text賦值:
binding.text = "測試"
- 首先隱藏我們原有的標題欄,在Activity的onCreate中加入下面的代碼:
4, 關(guān)于Activity文件中binding變量的一些說明
- binding變量的類型是ActivityMainBinding,這個是項目build后自動生成的,根據(jù)布局文件名:activity_main.xml 來命名的類名稱。也許你也發(fā)現(xiàn)了,它就是布局文件每個單詞首字母大寫,然后拼接上Binding。
- 我們前面說過,加入DataBinding后我們可以省去一些UI相關(guān)代碼,比如findviewById。那么具體是怎么操作呢。很簡單,在binding變量賦值后,我們直接通過binding.控件ID就可以直接獲取該控件實例。例如:binding.button.setOnClickListener(*)
- 我們還可以將xml文件中的variable進行賦值。具體見上面Activity代碼及相關(guān)注釋!
5,數(shù)據(jù)的實時更新,雙向綁定
在MainViewModel中和xml布局文件中新添加如下代碼:
// MainViewModel中
fun oneYearLater() {
age++
Log.d("MyTAG", "年齡:$age")
}
<!-- 在xml布局文件中 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{v -> viewModel.oneYearLater()}"
android:text="一年后"/>
當我們點擊這個按鈕一年后之后會執(zhí)行oneYearLater方法,里面的age屬性會自增。但是,這樣寫好之后,我們發(fā)現(xiàn)age變化了,但是視圖上的年齡的文字并沒有刷新。我們不是說加入了DataBinding之后會自動實時刷新嗎?別急,如果我們要實現(xiàn)實時刷新的話,我們要對MainViewModel進行小小的改造,其中有三種方法:
-
就像下面那樣,將對應(yīng)屬性改成這樣:
// var age = 15 var age = ObservableInt(15) fun oneYearLater() { // age++ val lastAge = age.get() age.set(lastAge + 1) Log.d("MyTAG", "年齡:$age") }將變量age聲明為可觀察的Int對象。其中,類似ObservableInt的變量類型還有:
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableDouble
- ObservableLong
...此處省略了一些基本數(shù)據(jù)類型
對于列表和Map,還有下面這些類型:
- ObservableList< T >
- ObservableArrayList< T >
- ObservableArrayMap<K,V>
- ObservableMap<K,V>
那么對于String或者自定義的類這種非基本數(shù)據(jù)類型,那么怎么辦?DataBinding給我們提供了:ObservableField<T>,我們就可以這樣用:
val name = ObservableField<String>("張三")對于序列化,還有這個數(shù)據(jù)類型:
ObservableParcelable< T >
-
讓類繼承BaseObservable:
我們先新建一個ObserveViewModel的類,讓它繼承BaseObservable:
class ObserveViewModel : BaseObservable() { private var firstName = "y" private var lastName = "c" // 這里要加上這個標簽,在set方法中BR才能找到對應(yīng)屬性 @Bindable fun getFirstName(): String { return firstName } @Bindable fun getLastName():String { return lastName } fun setFirstName(name:String) { this.firstName = name notifyPropertyChanged(BR.firstName) } fun setLastName(name:String) { this.lastName = name notifyPropertyChanged(BR.lastName) } // 改姓的方法 fun changeLastName() { setLastName("薛") } }在xml布局中進行引入,并且將對應(yīng)屬性值進行展示以及設(shè)定按鈕點擊事件:
<!-- 這段請放在data標簽內(nèi) --> <variable name="observeViewModel" type="top.cyixlq.test.ObserveViewModel"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{observeViewModel.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{observeViewModel.lastName}"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="改姓" android:onClick="@{v -> observeViewModel.changeLastName()}"/>別忘了還要在Activity中進行賦值:
val observeViewModel = ObserveViewModel() binding.observeViewModel = observeViewModel -
在Activity中進行監(jiān)聽。
在ObserveViewModel類中新添加一個屬性:
val age = ObservableInt(17)在Activity中新加入如下代碼:
// 給按鈕設(shè)置點擊監(jiān)聽事件 binding.btnAddAge.setOnClickListener { val lastAge = observeViewModel.age.get() observeViewModel.age.set(lastAge + 1) } // 監(jiān)聽ObserveViewModel中值的變化并進行回調(diào)處理 observeViewModel.age.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(observable: Observable, i: Int) { binding.age.text = observeViewModel.age.get().toString() } })在布局文件中新增一個TextView展示新的屬性,并添加一個按鈕改變新的屬性值:
<TextView android:id="@+id/age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="年齡"/> <Button android:id="@+id/btn_add_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加一歲"/>
看過上面三種方法后,是不是覺得第一種方法最簡單?是的,我個人也比較推崇第一種方法,簡單粗暴,但是并不意味著其他方法就用不到了,我們還是應(yīng)該根據(jù)業(yè)務(wù)需求使用不同的方法靈活變通!
了解Vue的同學(xué)知道,當我使用:value={{text}}的時候,就可以實現(xiàn)數(shù)據(jù)視圖雙向綁定。即輸入框中的內(nèi)容是什么,對應(yīng)的屬性值就是輸入框中的內(nèi)容。那么,DataBding也可以做到嗎?答案是當然可以的。首先我們在MainViewModel中新添加一個text屬性:
val text = ObservableField<String>("")
然后在activity_main.xml布局文件中多加一個EditText和一個TextView:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.text}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={viewModel.text}"/>
這樣做完之后,我們在輸入框中輸入什么,我們在TextView上面看到的就是什么。這樣就實現(xiàn)了雙向綁定。我們不然發(fā)現(xiàn),我們實現(xiàn)雙向綁定其實就是多加了一個“ = ”!