Android Jetpack之DataBinding(一)

DataBinding簡介

DataBinding綜述
DataBinding是Google Jetpack組件中的一員,該庫使用聲明性格式(而非程序化地)將佈局中的界面組件綁定到應用中的數據源。
使用該庫,藉助佈局文件中的綁定組件,您可以移除 Activity 中的許多界面框架調用,使其維護起來更簡單、方便。
還可以提高應用性能(綁定的時候遍歷一遍View,而不是findViewById每次遍歷查詢),並且有助於防止內存泄漏以及避免發生 Null 指針異常
(該庫進行了空指針判斷)。
DataBinding相關的類
  1. DataBindingUtil.java類:類,作爲Util在Activity中獲取相關的Binding對象。
  2. BaseObservable.java類:類,Bean可以繼承該抽象類,實現可觀察的模式,在set屬性的時候調用notifyPropertyChanged方法,喚起刷新操作,也可以調用notifyChange方法全部刷新。
  3. Observable .java接口:接口:Bean可以實現該接口,實現可觀察的模式,在set屬性的時候調用notifyPropertyChanged方法,喚起刷新操作,也可以調用notifyChange方法全部刷新。
  4. ObservableFloat.java類:類,這不是一個類,而是一類類的代表,如ObservableShort、ObservableParcelable等等,可觀察的屬性,通過get和set方法操作相關的值。
  5. BaseObservableField<>.java類:類,和上述類似,泛型可以傳入String等類型,比上述定義的基類型更加自由。

DataBinding使用

簡單使用
控件綁定
  1. 打開DataBinding開關,如下:
	android {
		  dataBinding{
       		 enabled = true
   		 }
	}

新版本需要在gradle.properties配置如下:

android.databinding.enableV2=true
  1. 改造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>
  1. 綁定後代碼如下代碼具體如下:
	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()
    }

}

效果如圖:
效果4

數據綁定
  1. 同上
  2. 改造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)
  1. 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 )
    }

}

  1. 設置數據到佈局文件,如下:
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)
    }

}

運行效果如圖:
效果1
如果不設置數據或設置數據爲null進去
則顯示默認值(但不會報空指針)如圖(左):,但不是default的值,default的值只在預覽視圖展示,方便調整佈局及預覽,如圖(右)
效果2效果3

使用詳解
  1. 表達式語言

可用

  • 算術運算符 + - / * %
  • 字符串連接運算符 +
  • 邏輯運算符 && ||
  • 二元運算符 & | ^
  • 一元運算符 + - ! ~
  • 移位運算符 >> >>> <<
  • 比較運算符 == > < >= <=(請注意,< 需要轉義爲 <)
  • 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>

報錯如下:
錯誤1

  1. 雙目運算符??進行 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"/>
  1. 列表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>

執行效果如下(此處超出下標是不會報錯的,但是也不會展示內容):
效果4

  1. 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&lt;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來取值,兩個是不一樣的
效果5

  1. 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&lt;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>

效果如下:
效果6

  1. 字符串字面量
    兩種寫法如下,效果是一樣的,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+`反單引號`}"/>

效果如下:
效果7

  1. 資源引用
    資源文件如下:
	<!--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)}"/>

效果如下:
效果8

  1. 可以視同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")
    }

}

效果如下:
效果9

注意:databinding不支持<merge>標籤(merge標籤寫的時候就會直接報紅,因爲)
寫的時候注意格式 bind:參數名(item中的參數名)= 值,同時導入命名空間 xmlns:bind="http://schemas.android.com/apk/res-auto",不
然會報紅,但是不影響運行,不寫bind:也不影響運行,但是建議按規範寫。
事件綁定
原有方法調用,如onTextChanged/onClick/onLongClick等
  1. 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"/>	

可以看到效果如下:
效果3

監聽綁定,

這個比較自由,方法名、參數都可以任意寫,代碼如下:

  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、自勉,多接觸一些自己沒有用到的東西,一方面提升自己的技能,另外一方面也是對自己思維的一個開拓,這是很不錯的選擇。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章