DataBinding簡介
DataBinding綜述
DataBinding是Google Jetpack組件中的一員,該庫使用聲明性格式(而非程序化地)將佈局中的界面組件綁定到應用中的數據源。
使用該庫,藉助佈局文件中的綁定組件,您可以移除 Activity 中的許多界面框架調用,使其維護起來更簡單、方便。
還可以提高應用性能(綁定的時候遍歷一遍View,而不是findViewById每次遍歷查詢),並且有助於防止內存泄漏以及避免發生 Null 指針異常
(該庫進行了空指針判斷)。
DataBinding相關的類
- DataBindingUtil.java類:類,作爲Util在Activity中獲取相關的Binding對象。
- BaseObservable.java類:類,Bean可以繼承該抽象類,實現可觀察的模式,在set屬性的時候調用notifyPropertyChanged方法,喚起刷新操作,也可以調用notifyChange方法全部刷新。
- Observable .java接口:接口:Bean可以實現該接口,實現可觀察的模式,在set屬性的時候調用notifyPropertyChanged方法,喚起刷新操作,也可以調用notifyChange方法全部刷新。
- ObservableFloat.java類:類,這不是一個類,而是一類類的代表,如ObservableShort、ObservableParcelable等等,可觀察的屬性,通過get和set方法操作相關的值。
- BaseObservableField<>.java類:類,和上述類似,泛型可以傳入String等類型,比上述定義的基類型更加自由。
DataBinding使用
簡單使用
控件綁定
- 打開DataBinding開關,如下:
android {
dataBinding{
enabled = true
}
}
新版本需要在gradle.properties配置如下:
android.databinding.enableV2=true
- 改造xml佈局,添加及標籤
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
</data>
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.TestActivity">
<TextView
android:id="@+id/tv_name"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginTop="10dp"
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
- 綁定後代碼如下代碼具體如下:
class TestActivity : AppCompatActivity() {
var binding: ActivityTestDataBindingBinding? = null
var student: Student = Student("小李", 23)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding
)
binding?.tvName?.text = "小張"
binding?.tvAge?.text = 22.toString()
}
}
效果如圖:
數據綁定
- 同上
- 改造xml佈局,添加及標籤,標籤用於引入數據如下:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable name="student"
type="com.zgj.demojetpackapp.bean.Student"/>
</data>
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.TestActivity">
<TextView
android:id="@+id/tv_name"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.name,default = Jack}"/>
<TextView
android:layout_marginTop="10dp"
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{student.age+"",default = 16}'/>
</LinearLayout>
</layout>
Student爲一個簡單的數據類,如下:
data class Student (var name:String,var age:Int)
- Rebuild(Kotlin需要,Java可以直接使用)之後綁定佈局文件,如下:
class TestActivity : AppCompatActivity() {
var binding: ActivityTestDataBindingBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_test_data_binding )
}
}
- 設置數據到佈局文件,如下:
class TestActivity : AppCompatActivity() {
var binding: ActivityTestDataBindingBinding? = null
var student: Student = Student("李大爺", 80)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding
)
binding?.student = student
//兩個等效
//binding?.setVariable(BR.student,student)
}
}
運行效果如圖:
如果不設置數據或設置數據爲null進去
則顯示默認值(但不會報空指針)如圖(左):,但不是default的值,default的值只在預覽視圖展示,方便調整佈局及預覽,如圖(右)
使用詳解
- 表達式語言
可用:
- 算術運算符 + - / * %
- 字符串連接運算符 +
- 邏輯運算符 && ||
- 二元運算符 & | ^
- 一元運算符 + - ! ~
- 移位運算符 >> >>> <<
- 比較運算符 == > < >= <=(請注意,< 需要轉義爲 <)
- instanceof
- 分組運算符 ()
- 字面量運算符 - 字符、字符串、數字、null
- 類型轉換
- 方法調用
- 字段訪問
- 數組訪問 []
- 三元運算符 ?:
不可用:
-
this
-
super
-
new
-
顯式泛型調用
如下代碼演示(包含方法調用,字符串拼接、三目運算符、運算符%等,目前instanceof還沒有調好):
<TextView android:layout_width="wrap_content"
android:text="@{String.valueOf(10%3)}"
android:padding="10dp"
android:visibility="@{1>2?View.GONE:View.VISIBLE}"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text='@{"新年"+"快樂"}'
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text='@{"123456".substring(0,2)}'
android:padding="10dp"
android:layout_height="wrap_content"/>
注意:使用View.GONE的時候要注意早標籤內導入類如下,不然會報錯:
<data>
...
<import type="android.view.View"/>
</data>
報錯如下:
- 雙目運算符??進行 Null 合併運算,如果左邊的值爲null,則取右邊的值,右邊也爲null,那就是null了。
代碼如下(等效於三目運算符):
<TextView android:layout_width="wrap_content"
android:text='@{student.name??"Tom"}'
android:padding="10dp"
android:layout_height="wrap_content"/>
<!--等效於-->
<TextView android:layout_width="wrap_content"
android:text='@{student.name != null?student.name:"Tom"}'
android:padding="10dp"
android:layout_height="wrap_content"/>
- 列表List的使用,代碼如下:
binding?.list = listOf("jack","tom","wang")
<LinearLayout android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:text="@{list[0]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{list[1]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{list[2]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
</LinearLayout>
執行效果如下(此處超出下標是不會報錯的,但是也不會展示內容):
- map的使用
kotlin代碼(可以看到只給name1、name2和name4賦值):
binding?.map = mutableMapOf(Pair("name1","jack"), Pair("name2","tom"),Pair("name3","wang"))
binding?.name1 = "name1"
binding?.name2 = "name2"
binding?.name33 = "name3"
binding?.name4 = "name4"
xml代碼:
<data>
...
<variable name="map" type="Map<String,String>"/>
<variable name="name1" type="String"/>
<variable name="name2" type="String"/>
<variable name="name33" type="String"/>
<variable name="name4" type="String"/>
</data>
<LinearLayout android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:text="@{map[name1]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{map[name2]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{map.name33}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{map[name4]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
</LinearLayout>
效果如下(可以看到第一個第二個展示出來了,但是第三個展示不出來,第四個因爲沒有對應的key-value所以沒有展示):
注意: map.name33 直接將name33作爲key來取值 map[name2]是將name2對應代碼設置的值作爲key來取值,兩個是不一樣的
- SpareArray使用
kotlin代碼
val sparse:SparseArray<String> = SparseArray<String>()
sparse.put(1,"jack")
sparse.put(2,"tom")
sparse.put(3,"wang")
binding?.sparse = sparse
xml代碼,效果:
<data>
<import type="android.util.SparseArray"/>
<variable name="sparse" type="SparseArray<String>"/>
</data>
<LinearLayout android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<TextView android:layout_width="wrap_content"
android:text="@{sparse[1]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{sparse[2]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{sparse[3]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
<TextView android:layout_width="wrap_content"
android:text="@{sparse[4]}"
android:padding="10dp"
android:layout_height="wrap_content"/>
</LinearLayout>
效果如下:
- 字符串字面量
兩種寫法如下,效果是一樣的,xml代碼如下:
<TextView
android:layout_marginTop="10dp"
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{student.age+""}'/>
<TextView
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{student.age+`反單引號`}"/>
效果如下:
- 資源引用
資源文件如下:
<!--dimen尺寸資源-->
<dimen name="dimen_10">10dp</dimen>
<dimen name="dimen_30">30dp</dimen>
<!--字符串資源-->
<string name="str_demo1">字符串Demo:%s 結尾</string>
<string name="str_demo2">字符串Demo:我有 %d 個香蕉</string>
<string name="str_demo3">字符串Demo:我叫 %s ,今年 %d歲了</string>
<string name="str_demo4">字符串Demo:我叫 %1$s ,今年 %2$d歲了</string>
<string name="str_demo5">字符串Demo:我有 %d個香蕉 ,%d個蘋果</string>
xml代碼如下:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="paddingResources"
android:padding="@{student.age > 50?@dimen/dimen_10:@dimen/dimen_30}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/str_demo1(`測試字符串`)}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/str_demo2(10)}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/str_demo3(student.name,student.age)}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/str_demo4(student.name,student.age)}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{@string/str_demo5(10,20)}"/>
效果如下:
- 可以視同include標籤,使用binding來傳入參數
傳入格式:bind:參數名(item中的參數名)=值
item的xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
<variable name="person" type="com.zgj.demojetpackapp.bean.Person"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_10"
android:text="@{`學生:`+student.name+`,`+student.age}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/dimen_10"
android:text="@{`人員:`+person.name+`,`+person.birthday}"/>
</LinearLayout>
</layout>
主xml如下:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
<variable name="person" type="com.zgj.demojetpackapp.bean.Person"/>
</data>
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.TestActivity">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="DemoMain"/>
<include layout="@layout/item_data_binding"
bind:student="@{student}"
bind:person="@{person}"/>
</LinearLayout>
</layout>
kotlin代碼如下:
class TestActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding :ActivityTestDataBinding2Binding= DataBindingUtil.setContentView(this,R.layout.activity_test_data_binding2)
binding.student = Student("小張",23)
binding.person = Person("老王","1980-10-10")
}
}
效果如下:
注意:databinding不支持<merge>標籤(merge標籤寫的時候就會直接報紅,因爲)
寫的時候注意格式 bind:參數名(item中的參數名)= 值,同時導入命名空間 xmlns:bind="http://schemas.android.com/apk/res-auto",不
然會報紅,但是不影響運行,不寫bind:也不影響運行,但是建議按規範寫。
事件綁定
原有方法調用,如onTextChanged/onClick/onLongClick等
- xml添加事件,代碼如下(此處方法調用可以是.也可以是::,推薦後者,防止混淆),效果見後圖:
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable name="student"
type="com.zgj.demojetpackapp.bean.Student"/>
<variable name="listener"
type="com.zgj.demojetpackapp.databinding.TestActivity.EventListener"/>
</data>
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.TestActivity">
<TextView
android:id="@+id/tv_name"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginTop="10dp"
android:id="@+id/tv_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:padding="10dp"
android:text="點擊事件1"
android:onClick="@{listener::onClick}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
2、代用代碼如下:
class TestActivity : AppCompatActivity() {
var binding: ActivityTestDataBindingBinding? = null
var student: Student = Student("小李", 23)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding
)
binding?.student = student
//重點 千萬不要忘記
binding?.listener = EventListener()
}
inner class EventListener{
fun onClick(view: View){
Toast.makeText(this@TestActivity,"點擊按鈕",Toast.LENGTH_SHORT).show()
}
}
}
此處注意方法名和入參都必須與監聽器一致,如點擊事件必須入參View等,如onTextChanged必須如下寫法:
這樣設置之後名稱根據EditText的內容變化,實現監聽
fun onTextChanged(s:CharSequence,start:Int,before:Int,count:Int){
Log.d(Common.TAG, s.toString())
binding?.student?.name = s.toString()
binding?.student = student
}
xml代碼如下:
<EditText android:layout_width="match_parent"
android:text="@{student.name}"
android:onTextChanged="@{listener::onTextChanged}"
android:layout_height="wrap_content"/>
可以看到效果如下:
監聽綁定,
這個比較自由,方法名、參數都可以任意寫,代碼如下:
fun customClickListener(student: Student) {
Toast.makeText(this@TestActivity, student.name, Toast.LENGTH_SHORT).show()
}
xml代碼如下(傳入的參數是lambda表達式方式,此處的調用是用.):
<TextView
android:padding="10dp"
android:text="點擊事件2"
android:onClick="@{()->listener.customClickListener(student)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
實現的效果如上圖,按鈕2的點擊事件就是這種寫法,只是此處的名字已經變了,所以彈出的是及時變化的名字。
說明
1. variable聲明xml中用到的變量,import導入需要用到的類
2. 導入的時候如果類名衝突,則可以使用別名alias來處理
3. 標籤的class屬性可以自定義生成類的類名
總結
1、關於Jetpack中的databinding限於篇幅先介紹到這裏,關於其他的內容後續第二篇的時候在介紹:
剩餘的內容包含:可觀察的字段和類、綁定適配器(配合RecycleView)、雙向綁定等。
2、關於上述使用databinding的代碼,都是經過驗證運行沒有問題的,如果有如環境等導致的問題歡迎探討。
3、databinding的使用感受:
1)有一定的方便,數據綁定也較爲安全,空指針等異常都得到了避免。
2)效率也有了提升,不用頻繁遍歷view,而是將view作爲變量維護了起來。
3)對於事件的處理要建一個監聽類導入進來,另外參數方面也有限制(方法調用),不是很方便。
4)報錯不夠精確,xml中的錯誤有時候能檢測到(如類沒有導入等),有時候提示很模糊(表達式語言有的錯誤檢測不出來),不知道哪裏
出錯了,調試起來不是太方便。
5)總體的感覺還是不錯的,有效的減少了activity的代碼量,xml使用起來也比較明確,整體還是建議大家嘗試的,至於項目中是否使用,
那就要在使用之後自己做整體的評估了。
3、自勉,多思考,多實踐,不要只看網上的結論,不都是正確的,有時候由於各方面的條件限制得出的特定結論是不通用的。
4、關於文檔,不要只停留在看的層面上,如上述的表達式語言的使用,官網上也就簡單的幾句demo,自己看的時候要學會舉一反三,
同時也是查漏補缺的過程。
5、自勉,多接觸一些自己沒有用到的東西,一方面提升自己的技能,另外一方面也是對自己思維的一個開拓,這是很不錯的選擇。