DataBinding 的簡單使用

先吐槽下,不說不爽,不說不通達

不吐不快,集合我這幾天學習 DataBinding 的經歷說幾句。DataBinding 這東西也不是 android 的專利,android 引入這個功能是后知后覺的,一步一趨的跟著別的系統腳步發展的。縱觀全局,14-16年是 android 技術大爆發的年頭,各種新技術層出不窮,目不暇接,到17年中呢android 技術的進步就停下來了,android 開發呢也是進入生命周期內最輝煌的時候,技術已經非常成熟了,可以遇見的未來,android 的技術短期內沒有什么大的進步,改變了。作為一個普通的 android 開發者,我們要在這個時間點上努力的吸收之前幾年 android 開發技術的精髓,和各種優秀的思路,思想,手段和套路,我覺得這就是我們做 android 開發應該詳細了解,舉一反三的,這些也移動開發的核心精髓,換個平臺,換個系統,除了基礎的開發語言和構建工具,和系統知識體系的變化外,剩下的都是要重復或者再走 android 這些年這些技術發展的老路,我認為相同領域技術思路都是趨同的,區別是不同平臺,不同語言的具體實現罷了。


什么是 DataBinding

什么是 DataBinding 呢,簡單說來就是幫我們實現 view 和 data 綁定的工具,把數據映射到 view 的 xml中,可以在 xml 布局文件中實現 view 的賦值,方法調用。使用 DataBinding 后,我們不同再寫 findViewById,不用再獲取控件對象,不用再設置監聽,可以節省我們 activity 中的很多獲取控件,賦值,添加監聽所需要的代碼。

DataBinding 是個好東西啊,15年 google IO 大會就開始推了,最直接的變化就是催生了 android 中 MVVM 的出現,MVVM = MVP + DataBinding 。一線公司早就普及 MVVM 了,大伙作為一個普普通通的 andoid 開發者,一定要身上時代,什么是時代,大廠就是時代,大廠都在干什么,我們就干什么,不求跟上大廠,但求不落后太多,所以小伙伴們走起,MVVM 作為 MVP 的進階,我們一定要學好,這期中的重中之重 DataBinding 一定不要落下,其實沒多難,DataBinding 初步學習半天就可以,其中涉及到列表和多module的部分是比較復雜 的,我們多看幾個開源的 app 例子就行,這里我也盡量詳細的說一說。經驗是干什么的,就是帶著我們少走彎路,快速學習的,有的可以快,有的不能快,必須去體會,恰巧 DataBinding 就是可以快起來的部分。


DataBinding 的初步使用

寫了一段時間的博客后,我是深深體會到了,一不論多復雜的事一定要按步驟拆解成一段段簡單的步奏,這樣才能學的快,學的明白,中間斷掉了,之后也好撿起來繼續學。

千里之行,始于足下,所以呢,我們先來把 DataBinding 集成進來,做個最簡單的實現。

先導入 DataBinding 功能

因為 DataBinding 是 google 強推的嘛,所以 1.5版以上 gradle 就自帶 DataBinding 庫了,只要我們在 gradle 配置中打開 DataBinding 這個功能就可以了,不用再引入遠程依賴了。哪個 module 需要就在哪個 module 的 build.gradle 編譯配置文件中聲明啟動 dataBinding 就可以了。注意 Gradle 最低要 1.5 alpha 版。

android {
    ...
    dataBinding {
        enabled = true
    }
    ...
}

這里我在 app 這個 module 里面聲明的。DataBinding 在 gradle 中的表現形式就是 DataBinding 相關的 task 編譯任務了,我們 enabled = true 之后,在編譯時就會執行 DataBinding 的 task 了。

在 xml 中使用
<?xml version="1.0" encoding="utf-8"?> 
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    
    <data>
        <import type="com.bloodcrown.bwdatabindingdemo.Book"/>
        <variable name="book" type="Book"></variable>
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{book.name}"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_editor_absoluteY="20dp"
            tools:text="name"/>

        <TextView
            android:id="@+id/tv_price"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@{book.price}"
            app:layout_constraintLeft_toLeftOf="@id/tv_name"
            app:layout_constraintTop_toBottomOf="@id/tv_name"
            tools:text="price"/>

    </android.support.constraint.ConstraintLayout>
</layout>

注意 dataBinding 在 xml 中的使用規則:

  • 使用 DataBinding 必須要使用 <layout> 這個標簽作為 xml 的根部局
  • 所有 DataBinding 的內容都寫在 <data >標簽內
  • <import > 是導入所使用的對象的類型
  • <variable > 是具體聲明一個數據對象,<name> 是這個對象的名字,<type> 是這個對象的類型,<variable > 可以有多個
  • 使用 @{ } / @{book.price} 的方式可以把這個聲明出來的數據對象的莫個數據設置給我們的 view
java 代碼的使用部分
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        Book book = new Book("AAA", "35");
        mainBinding.setBook(book);
    }
}
  • 首先我們使用 DataBinding 的方式替換我們的 setContentView,使用 DataBindingUtil 這個類的 setContentView 方法,里面傳入activity 和 layout 布局
  • 然后 DataBinding 的 setContentView 方法會根據類名給我們返回一個 DataBinding 的輔助類,這里就是這個 ActivityMainBinding 了。名字規則是 Activity 類型名在前 + 類中其他的名字,這個 ActivityMainBinding 輔助類我們不用編譯 AS 也會自動幫我們生成,但是呢要是你在這里有問題,那就手動編譯一下。
  • 我們拿到輔助類之后,new 一個數據對象出來,然后把這個數據對象設置給輔助類 ActivityMainBinding,因為我們在 xml 中的<data>中聲明幾個數據對象,那我們在 activity onCreate 中就得給 DataBinding 的輔助類 ActivityMainBinding,設置幾個數據對象進去,然后 DataBinding 才可以根據我們設置進來的數據對象,給相關 view 設值。

DataBinding 的數據更新

view 在本質的職責是反應數據的變化,那么我們就來說說在 DataBinding 中我們怎么更新數據。DataBinding 的數據本質是我餓們在 xml 中 <data> 標簽中聲明的數據對象,我們在java 代碼中把相關的數據對象設置給 DataBinding 的輔助類,那么我們有一下幾種方式:

  • 整體再設置一次數據 bean 對象,這個適合更新整體數據
  • 使用 BaseObservable ,操作數據 bean 的 set 方法更新字段數據,適合更新部分數據
  • 使用 ObservableFields ,只更新有需求的數據字段,這個不夠靈活
  • 使用 DataBinding 也有的集合類型:ObservableArrayMap , ObservableArrayList
  • 雙向數據綁定
更新整體數據

這個本質上就是重復一次我們給 DataBinding 輔助類設置數據的過程,使用很簡單

 Book book = new Book("BBBB", "65");
 mainBinding.setBook(book);

這樣就可以了,說實話,我更傾向于這樣方式,因為大多數時候,數據都是休要整體更新的

使用 BaseObservable

BaseObservable 是一個基類,需要我們的數據 bean 繼承這個基類,然后給屬性的 get 方法
添加@Bindable 這個注解,然后在屬性的 set 方法中添加上 DataBinding 更新某個字段的方法

public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    } 

這樣做呢,其實是在 DataBinding 的輔助類中把相關屬性的更新和 view 的賦值方法關聯在一起,完整的數據 bean 如下:

public class Book extends BaseObservable {

    public String name;
    public String price;

    public Book() {
    }

    public Book(String name, String price) {
        this.name = name;
        this.price = price;
    }

    @Bindable
    public String getName() {
        return name;
    }

   public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(BR.name);
    }

    @Bindable
    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
        notifyPropertyChanged(BR.price);
    }
}
在 activity 中操作數據 bean 的 set 方法就可以同步把數據更新到 view 中了
mBook.setName("CCC");

有個更好的解釋:

Obserable接口有一個自動添加和移除監聽器的機制,但是通知數據更新取決于開發者。為了使開發變得簡單,谷歌創建了BaseObserable這個基礎類來集成監聽器注冊機制。通過給getter方法添加Bindable注解,通知setter方法。

使用 ObservableFields

ObservableFields 是一個對屬性添加 DataBinding 更新功能的代理類,針對不同的數據類型有不同類型的 ObservableFields :ObservableBoolean、 ObservableByte ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、 ObservableParcelable 等。

這種方式不是主流類型,使用不便,不能擴展屬性,所以這里我簡單的放個例子:

// 數據 bean 中聲明一個屬性
 public ObservableField<String> age = new ObservableField<>(); 

// activity 中更新數據
 mBook.age.set("ABABA");
ObservableArrayMap , ObservableArrayList

這2個集合類型是 DataBinding 為了方便數據刷新提供的,不用我們再手動通知集合的變化,只要我們更新了集合的某些數據,就能自動更新相關的 view 的數據

簡單舉個例子:

// 聲明一個集合對象
private ObservableArrayMap<String, Object> mapUser = new ObservableArrayMap<>();

// 這樣直接更新集合數據就可以了
mapUser.put("firstName", "zhu");
mapUser.put("lastName", "chen");
mapUser.put("age", 27);
雙向數據綁定

雙向數據綁定就是當 view 改變時,data 會跟著改變;data 改變時,view 會跟著改變,核心就是給 view 指定 :@={book.name},這里用 EditText 做個例子

  <EditText
            android:text="@={book.name}"
/>

這樣當輸入不同的內容時,數據就會同步更新到綁定的 book 這個數據對象中。


DataBinding 的更豐富使用

上文書書說 DataBinding 的精髓都在布局的 xml 文件中,其中有著豐富的操作,DataBinding 在xml 有越多的用法,那么就會越多的替代我們 java 中的代碼,越多的減少 activity 的代碼量,而且使用 DataBinding 后,頁面的賦值邏輯在 xml 也會顯得剛加清晰,可見。那么現在我們就來看看 DataBinding 都有那些玩法。

1. variable 標簽支持的數據類型

java 能使用的 variable 標簽當然都能

  • 基本數據類型,我們按照 java 的寫法即可,比如 String,int
  • 若是引入了名字相同的類,可以給類添加別名
  • 引用類型使用 import 引入全包名即可
  • list,map 結合類型同引用類型 import 導下包名就行
// 引用類型
<import type="com.bloodcrown.bwdatabindingdemo.Book"/>
<variable name="book" type="Book"></variable>

android:text="@{book.name}"

------------------------------------

// 基本數據類型
// 基本數據類型不用 import 導入,直接在 type 類型里寫就行qw,寫 int 可能會報錯,忽略就行,寫相應的包裝類型就得引入包了
<variable name="name" type="String"></variable>
<variable name="price" type="int"></variable>

// 在使用 int 等基本數據類型時,注意轉成字符串再賦值,DataBinding 不會幫我們做類型轉換的
android:text="@{String.valueOf(price)}"

------------------------------------

// 集合數據類型
// 集合的使用方式包括 [] 和 get 2種方式
<import type="java.util.List"/>
<import type="java.util.Map"/>
<variable name="bookList" type="List&lt;Book&gt;"></variable>
<variable name="bookMap" type="Map&lt;String,Book&gt;"></variable>

android:text="@{bookList.get(1).name}"  
android:text="@{bookList[1].price}"  
android:text='@{bookMap["111"].name}'
android:text='@{bookMap.get("111").price}'
// 注意其中特殊特好的使用,集合枚舉的 <> 符號直接寫xml 不認,需要用轉移符號,寫 map 時,key 要是 String 的,那么你可以再里面用 " " ,但是這行的 xml 外面就得用 ' ' 才行,這點注意啊,要不 xml 總是報錯

------------------------------------

// 設置別名
// 我們引入的不同包的類可能重名,那么自然我們需要加一個別名來加以區分了,用alias表示
<import type="com.zx.databindingdemo.bean.UserBean" />
<import type="com.zx.databindingdemo.bean.user.UserBean" alias="UserBean2"/>

<variable  name="user"  type="UserBean" />
<variable  name="user2"  type="UserBean2"/>

常用的轉義字符表

2. 如何調用,注冊各種方法

大家想啊,既然在 xml 中都可以直接操作屬性值了,那么我調用一個方法還不是妥妥的啊。這里要區分方法的調用和監聽方法的注冊

對于一個 button 的點擊事件來說,我們可以走下面2中方式:

  • DataBinding 會根據 id 名,生成相應的 view 對象,然后我們給這個 view 設置監聽
 mainBinding.btnTest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText( MainActivity.this,"AAA",Toast.LENGTH_SHORT ).show();
            }
        });
  • 在 xml 中聲明一個點擊事件的對象,然后設置進 android:onClick 屬性里
// 先聲明點擊事件對象
 <import type="android.view.View.OnClickListener"/>
 <variable name="testClick" type="OnClickListener"></variable>

// 再使用
 <Button  android:onClick="@{testClick}"  />

// activity 里填充這個點擊事件對象進去
mainBinding.setTestClick(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText( MainActivity.this,"BBB",Toast.LENGTH_SHORT ).show();
            }
        });

總體感覺和原來的注冊方式沒啥區別

除了各種事件的注冊外,我們在利用 DataBinding 可以在 XML 中使用各種方法,靜態方法,和對象中的方法都是可以直接調用的,注意是在給 view 屬性賦值時可以直接調用

// Utils 里面有一個靜態方法

// 導入包含靜態方法的類,DataBinding 中要想使用任何類型,除了基本數據類型,都得導包
 <import type="com.bloodcrown.bwdatabindingdemo.Utils"/>
<import type="com.bloodcrown.bwdatabindingdemo.MainPersenter"/>
<variable name="persenter" type="MainPersenter"></variable>

// 方法直接使用即可,直接傳參也是可以的
 <Button
            android:text="@{Utils.getName(book.name)}"  

 <Button
            android:text="@{  android:text="@{persenter.getPrice()}"}"  
3. DataBinding 對 lambda 表達式的支持

DataBinding 支持我們直接在 xml 書寫 lambda 表達式,常用的就是注冊 click 點擊事件了,在 view 的 onClick 屬性中我們需要傳入一個對象,通過上面的內容學習,我們是聲明了一個 ClickListener 類型的對象數據出來,然后在 java 中傳入這個對象的方式做的。但是我們在這里可以直接實現 lambda 表達式書寫一個匿名實現類對象出來,這樣就省了我們在 java 中傳入對象的代碼了。這里說一下 lambda 表達式就是對匿名實現類對象的簡寫,所以我們雖然看著像是調用了方法的樣子,其實我們是寫了一個匿名實現類對象出來,本質上還是傳入了一個對象。不熟悉 lambda 表達式的看這里:Lambda表達式以及AS 對其的支持 ,還是推薦大家去學習一下 Lambda的,現在各大語言都在往這種函數式編程上靠,像 lambda 表達式這種從函數式編程上借鑒過來的東西,以后只會越來越多的,抗拒這種變化是不明智的。

大家可能會這么寫 Lambda 表達式

 <Button
            android:onClick="@{() -> persenter.speak(book.name)}"

但是很遺憾,你這么寫會報錯,一個類型轉換異常的錯誤,知道為什么嗎?這個還是要從 Lambda表達式說起。Lambda 的特征是隱藏實現類的 類名方法名,因為 java 8可以知道從上下文(方法中對于參數的限定)知道類的類型,并且規定了類的里面只能有一個方法,那么這里我們使用的就是這個方法:

 public void onClick(View v) {
                ......
           }

注意我們使用 Lambda 重寫的就是這個 onClick 方法,onClick 方法要求傳入一個 view 的參數的,上面我們沒有傳這個參數,所以報錯了。那么我們把 Lambda 表達式修改一下:

 <Button
            android:onClick="@{(view) -> persenter.speak(book.name)}"

view 表示點擊事件的 view 對象,這樣 Lambda 就可以跑了。不過呢,使用 Lambda 寫點擊事件對象是屬于動態類型的,因為每點擊一次都會從心生成一個 點擊的 匿名實現類 設置 view,所以注意這個動態的特性,合理利用,因為這樣點擊事件的數據也是可以使用新的了。

4. 其他一些監聽器
除了 onClick 之外,還提供了一些特定的點擊事件,這里需要注意,下面幾個我沒用到過,也是從別人那里摘過來的:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

這里不知道為啥出現2個相同的表格出來,郁悶啊

5. 支持表達式語言

表達式語言和java語法很相似。以下是相同的:

  • Mathematical: + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

6. 不支持的操作

java中的一些特性在DataBinding語法中不支持

  • this
  • super
  • new
  • Explicit generic invocation

7. Null Coalescing 空運算符

android:text="@{user.displayName ?? user.lastName}"

就等價于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

8. 關于字符串的符號再次說明一下

可以在屬性值使用單引號,在表達式中字符串的值使用雙引號:

android:text='@{map["firstName"]}'

也可以在屬性值使用雙引號,表達式中的值字符串的值應該使用單引號或者是"`"。

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"

9. 自定義 DataBinding 輔助類類名

默認情況下,binding 類的名稱取決于布局文件的命名,以大寫字母開頭,移除下劃線,后續字母大寫并追加 “Binding” 結尾。這個類會被放置在 databinding 包中。舉個例子,布局文件 contact_item.xml 會生成 ContactItemBinding 類。如果 module 包名為 com.example.my.app ,binding 類會被放在 com.example.my.app.databinding 中。

通過修改 data 標簽中的 class 屬性,可以修改 Binding 類的命名與位置。

<data class="CustomBinding">
    ...
</data>

以上會在 databinding 包中生成名為 CustomBinding 的 binding 類。如果需要放置在不同的包下,可以在前面加 “.”

<data class=".CustomBinding">
    ...
</data>

這樣的話, CustomBinding 會直接生成在 module 包下。如果提供完整的包名,binding 類可以放置在任何包名中

<data class="com.example.CustomBinding">
    ...
</data>

10. DataBinding 中使用資源文件

資源 id 方面我們同傳統方式去寫就行,但是還是稍有些差別的。不如看這個例子:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

運行起來,可能有的版本會報錯誤,因為這是 DataBinding 的 bug,有人說修了,有人說還有,碰上的朋友這樣改

android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"

因為 DataBinding 生成數據的數據格式可能和我們實際需要的不同,注意這點。

其他的例子我們舉一個,替換 String.xml 中的占位符:

// 先聲明字符串資源
<string name="nameFormat">Full Name: %1$s:%2$s</string>

// DataBinding 在 xml 中可以直接引用使用
android:text="@{@string/nameFormat(firstName, lastName)}"

DataBinding 對于資源的文件頭命名可能和 android 傳統的有些地方不一樣,下面的表是官方文檔上的,找資料的話都是這張表,沒有其他的資料,大家實際要到問題可以參考這樣表,但是優先還是按照 android 原先的資源引用方式來:


簡書這幾天寫列表有問題,截圖頂一下

11. DataBinding 中使用資源文件


DataBinding 使用技巧

1. 對 include 的支持

xml 布局中不可避免的要使用 include 標簽,那么 DataBinding 怎么兼容這個 include 標簽呢。其實只要 外層的 xml 把 include 聲明的 DataBinding 數據對象傳給 include 就行,但是要注意 DataBinding 不支持 include 的 merge 標簽

include 的布局 name.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.liangfeizc.databinding.model.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.firstName}" />
    </LinearLayout>

</layout>

總體的布局文件

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <import type="com.liangfeizc.databinding.model.User" />
        <variable
            name="user"
            type="User" />
        <variable
            name="listener"
            type="com.liangfeizc.databinding.listener.OkListener" />
        <variable
            name="okText"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <include
            android:id="@+id/layout_input"
            layout="@layout/layout_input" />

        <include
            layout="@layout/user"
            app:user="@{user}" />

        <include
            layout="@layout/layout_btn_ok"
            app:okText="@{okText}"
            app:listener="@{listener}"/>
    </LinearLayout>
</layout>

另一個 include 的 xml ,layout_input.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

</layout>

注意啊,如何調 include 布局的某一個組件呢

  binding.layoutInput.etName.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                User user = new User(s.toString(), "Liang");

                binding.setUser(user);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

2. 對 fragment 的支持

上面我們演示的 activity,是通過 DataBinding 通過 setContentView 方法實現 DataBinding 和 activity xml 布局實現綁定的,那么問題來了 fragment 怎么辦

activity 我們這么寫

private ActivityDemoBinding mBinding;
protected void onCreate(Bundle savedInstanceState) {
    mBinding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
 }

Fragment 我們這么寫:

private FragmentHomeBinding mBinding;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    mBinding = DataBindingUtil.inflate(inflater, R.layout.homepage_fragment, container, false);
    return mBinding.getRoot();
}

3. 對 ViewStub 的支持

// 我們給 ViewStub 設置初始化函數
 mainBinding.bookStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                bookStubBinding = DataBindingUtil.bind(inflated);
                bookStubBinding.setBook(mBook);
            }
        });

// 模擬一個點擊事件加載 ViewStub 
 mainBinding.setTestClick(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!mainBinding.bookStub.isInflated()) {
                    mainBinding.bookStub.getViewStub().inflate();
                } else {
                    mBook.setName("stub");
                    mBook.setPrice("888");
                }
            }
        });

4. DataBinding 中的注解

DataBinding 可以使用的注解:

  • @BindingMethod :修改view 某個方法名字
  • @BindingAdapter : 給 view 添加set/get 方法,相當于添加自定義屬性
  • @BindingConversion : 提供數據類型轉換方法

@BindingMethod 用的不多,這里就不說了,我們來看下 @BindingAdapter 這個注解,可以給 view 添加自定義屬性,相當的好用啊,我們不用再定義復雜的 view 自定義屬性了,@BindingMethod 書寫簡單,功能強大,xml 中的自頂提示很友好。

先定義給 view 設置這個自定義屬性的具體執行方法

public class TextBingUtils {

    @BindingAdapter("info")
    public static void setInfo(TextView view, String info) {
        view.setText(info);
    }

}

注意 @BindingAdapter 注解里面的參數就是這個自定義屬性的名字,在 xml 里 app:xx 就可以使用了。另外這個方法寫在哪里都沒事,但是方法必須是靜態的,DataBinding 框架在編譯時會自動檢索 @BindingAdapter 這個注解的所有方法。

然后在就可以在 xml 中使用了

 <TextView
            app:info='@{"info"}'
/>

@BindingConversion 用的不多,但是也得說一下,這個注解會給 DataBinding 提供默認的2個數據類型之間的轉換方法,可以放置一些類型轉換錯誤,寫法上和 @BindingAdapter 相同。但是使用這轉換器屬性時我們必須要小心,因為DataBinding是不懂得區分是否真的需要使用整個轉換器的。比方說我們創建兩個相同類型的轉換方法,DataBinding 就只會使用第一個

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
<View
    android:background="@{isError.get() ? @color/red : @color/white}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_height="@{height}" />

DataBinding 對 RecyclerView 的優化

為啥要單開一章說呢,因為的確是太重要了,DataBinding 對 RecyclerView 支持真的是一大亮點啊,直接的就是使用 DataBinding ,我們就不用再寫 Viewholder 了。Viewholder 的工作就是 findviewbyid 持有相應的 view 的引用好讓我們來設置數據,DataBinding 會自動根據 view 的 id 生成相關的對象,至少從這點出來,DataBinding 對我們都是非常有意義的了。好了,來看看在 RecyclerView 中使用 DataBinding 的基礎方式。

新的 ViewHolder 書寫方式,每個 ViewHolder 只需要持有相應 view 對應的 DataBinding 輔助類對象,通過他恩那個找到所有的子 view 引用。

public class BookBindingViewHolder extends RecyclerView.ViewHolder {

    private ItemListBinding t;

    public BookBindingViewHolder(ItemListBinding t) {
        super(t.getRoot());
        this.t = t;
    }

    public ItemListBinding getBinding() {
        return t;
    }

    public void setT(ItemListBinding t) {
        this.t = t;
    }
}

item 的布局文件,要相生成 DataBinding 輔助類,必須在 xml 中顯示使用 DataBinding

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
>

    <data>
        <import type="com.bloodcrown.bwdatabindingdemo.Book"/>
        <variable name="book" type="Book"></variable>
    </data>

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="@{book.name}"
        android:textColor="@color/colorPrimary"
        android:textSize="22sp"
    />
</layout>

新的 adapter 通過 DataBinding 類刷新數據

public class BookBindingAdapter extends RecyclerView.Adapter<BookBindingViewHolder> {

    private List<Book> data;

    public BookBindingAdapter(List<Book> data) {
        this.data = data;
    }

    @Override
    public BookBindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
    }

    @Override
    public void onBindViewHolder(BookBindingViewHolder holder, int position) {
        Log.d("AAA", "position:" + position);
        holder.getBinding().setBook(data.get(position));
        holder.getBinding().setVariable(BR.book, data.get(position));
        holder.getBinding().executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

    public void setData(List<Book> data) {
        this.data = data;
    }
}

holder.getBinding().executePendingBindings() 這句話是刷新界面,否則可能會出現這個問題: RecyclerView使用databinding出現數據閃爍問題

另外注意列表在更新數據時也可以使用 holder.getBinding().setVariable(BR.book, data.get(position)); 這個方法是更新xml 里面定義的數據對象的,BR.book 是個 int 值,指向xml 中聲明的數據對象的id地址

當然上面這是最簡單的 DataBinding 列表實現,甚至多 itemType 都不支持,下面優化下多 itemType 的問題

// 寫一個返回 itemType 的接口,然后數據 bean 實現這個接口

public interface IBaseBindingAdapterItemProvide {
    int getItemType();
}

// 然后處理一下 adapter ,能夠兼容多類型的 item

public class CatBindingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<IBaseBindingAdapterItemProvide> data;

    public CatBindingAdapter(List<IBaseBindingAdapterItemProvide> data) {
        this.data = data;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == R.layout.item_list) {
            return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
        }
        if (viewType == R.layout.item_cat) {
            return new CatBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_cat, parent, false));
        }
        return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if (holder instanceof BookBindingViewHolder) {
            Book book = (Book) data.get(position);
            ((BookBindingViewHolder) holder).getBinding().setBook(book);
            ((BookBindingViewHolder) holder).getBinding().executePendingBindings();
            return;
        }

        if (holder instanceof CatBindingViewHolder) {
            Cat cat = (Cat) data.get(position);
            ((CatBindingViewHolder) holder).getBinding().setCat(cat);
            ((CatBindingViewHolder) holder).getBinding().executePendingBindings();
        }
    }

    @Override
    public int getItemCount() {
        return data == null ? 0 : data.size();
    }

    @Override
    public int getItemViewType(int position) {
        return data.get(position).getItemType();
    }

    public void setData(List<IBaseBindingAdapterItemProvide> data) {
        this.data = data;
    }
}

恩,現在可以支持多類型的列表了,但是要是我們在實際開發中對這段原始代碼不經任何雕琢(封裝,設計),那么說明我們做開發真的是沒長進。能夠初步封裝根據效果可以認為是初中級水平,能夠寫封裝出一個易于擴展的優秀的庫出來,可以視為高級水平了。期望大家多多用心,在封裝自己的庫時,是水平提升最快的時候了,知識點的學習是偏記憶,理解,考研智商和記憶能力。那么封裝原始代碼為庫就是考研我們的基礎代碼水平了,設計到的都是硬知識點,也是最難以提升的部分,需要大毅力才行,但也是最重要的代碼技能了。

本文先于篇幅,對于用 DataBinding 來優化 RecyclerView 就寫到這里了,更多更優秀的內容我會開單章的,放到通過 MVP 學習代碼封裝的那部分里。


最后

  1. 不要拒絕 findViewById
    DataBinding 和 findViewById() 并不是互斥的,DataBinding的源碼里面也是用到了findViewById()。如果某些情況真的不適合使用DataBinding,那就用回findViewById吧。

  2. xml中的表達式盡量簡單
    xml 文件中不要出現過于復雜業務邏輯,只出現簡單的 UI 相關的表達式。不要以為Data Binding是萬能的,而想盡辦法把邏輯寫在xml中。往往這個時候你就應該將邏輯寫在綁定的ViewModel里面。

  3. 注意clean
    有時候因為修改接口等原因要修改綁定的bean類,這時候偶爾會遇到一些神奇的bug。不要慌張,直接clean一下項目再make project一次往往就好了

  4. 使用BindingConversion注解時需要慎重
    原因上面已經說了。但是它也不失為一個很好用的注解,比方說使用它來進行字體的自定義。具體可以參照下面的文章:使用DataBinding來進行字體的自定義


kotlin 支持

在 kotlin 中使用,還要在 gradle.properties 中配置一個參數

android.databinding.enableV2=true

接下來,在布局文件當中,選中根布局的ViewGroup,然后按住 「Alt + 回車鍵」 如圖


16abb5f5b0765477.png

參考資料

這里有一個 DataBinding 的使用規范的文章,不是贊同所有內容,但還是推薦大家看看

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。