如何使用Data Binding Library(一)

1.前言


不知道是否還記得前段時間講得Android三種主流開發(fā)框架喳张,要是忘了,可以回顧一下笨使。當時提到了MVVM框架較MVP的優(yōu)勢在于將Model和View實現(xiàn)了雙向綁定,使得兩者之間可以互相傳遞變化僚害,減輕了Presenter的壓力硫椰。谷歌提供了Data Binding庫來實現(xiàn)綁定操作繁调,降低了開發(fā)的難度,詳細信息可以看官方開發(fā)庫靶草。若是覺得英文不方便蹄胰,有牛人已經(jīng)翻譯了文檔
  但是由于內容較多奕翔,初次接觸時不容易抓住重點裕寨。我結合自己的理解,大概地說一下思路派继,希望能幫助到大家宾袜。

2.核心思想


既然要實現(xiàn)雙向綁定,數(shù)據(jù)的傳遞必不可少驾窟。View層主要是布局的同時可以做簡單的操作庆猫,而不僅僅是配置界面;Model層則是加入觀察機制绅络,當數(shù)據(jù)變化時通知界面刷新月培。
  從編碼層次來說,一方面向XML中引入了類恩急、變量杉畜、表達式和事件處理。其中衷恭,變量可以承載數(shù)據(jù)寻行,類(常量及靜態(tài)方法)配合表達式控制展示,事件處理用于響應交互匾荆。另一方面實體類或其成員變量使用Observable接口拌蜘,使setters中封裝了通知方法,可以與界面交互牙丽。最終在Activity简卧、Fragment或Adapter等需要界面和數(shù)據(jù)的地方,為兩者建立聯(lián)系烤芦。

3.類和變量


想在XML中寫代碼举娩,首先得解決數(shù)據(jù)來源問題,所以引入<layout>构罗、<data>铜涉、<variable><import>這幾個標簽。根據(jù)以下代碼來講解一下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <import type="android.view.View"/>
       <variable name="user" type="com.example.User"/>
   </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:text="@{user.firstName}"/>
       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
   </LinearLayout>
</layout>
3.1.layout和data標簽

在Data Binding中遂唧,使用<layout>作為根標簽包裹兩個部分芙代,其一就是<data>包裹的數(shù)據(jù)區(qū),其二自然是以前的界面布局盖彭。由此可推斷纹烹,外界將數(shù)據(jù)傳入數(shù)據(jù)區(qū)聲明的變量中页滚,再由界面布局的表達式調用,最后賦值給控件屬性铺呵。
  數(shù)據(jù)是在Java代碼中以對象的形式傳入裹驰,所以在代碼中得有類來表示XML。不用操心片挂,它由Data Binding自動生成幻林,根據(jù)布局文件的名稱,轉化為駝峰命名方式音念,并添加“Binding”為后綴沪饺。默認放在模塊包的databinding目錄下,可通過在XML中為<data>添加class屬性來修改症昏。至于使用随闽,后面在Binding對象中將詳細說明。

// 只是修改生成類的名稱
<data class="ContactItem">
    ...
</data>
// 放置在相對模塊包的路徑下肝谭,并重命名
<data class=".ContactItem">
    ...
</data>
// 使用絕對路徑掘宪,與完整類名一致
<data class="com.example.ContactItem">
    ...
</data>
3.2.variable標簽

用來聲明XML中使用的變量,由兩個屬性組成攘烛,name描述在表達式中引用的名稱魏滚;type則是引用的類型。注意的是坟漱,type的值除了java.lang.*包下的類型不需要全路徑鼠次,其它都得寫完整。為了避免空指針芋齿,Data Binding提供了缺省值腥寇,引用類型為null,int類型為0觅捆,boolean類型為false等赦役。同時自動根據(jù)根視圖提供context對象,當同名時將被覆蓋栅炒。

當有多種不同配置的layout文件時(如掂摔,橫向或縱向),Variables會被合并赢赊,所以這些layout文件之間必須不能有沖突的Variable定義乙漓。

3.3.import標簽

用來聲明XML中使用的類,也有兩個屬性释移,type與<variable>的type作用一致叭披,當<import>中聲明后,variable中只要使用類名即可秀鞭;alias是別名趋观,當有多個類名一樣時扛禽,可以通過它設置別的名字區(qū)分锋边,與<variable>的name有些像皱坛。

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>

    <import type="android.view.View"/>
    <import type="com.example.real.estate.View" alias="Vista"/>
 </data>

<import>的作用和Java代碼中的一樣,所以在表達式中可以用于強制類型轉換豆巨,調用類的靜態(tài)變量和靜態(tài)方法。

4.表達式


這是代碼的關鍵部分。為了在XML中寫界面相關的邏輯飞苇,大部分與Java表達式保持一致澎语,只有以下幾點需要注意:

  • @{...}包裹表達式,方便與XML進行區(qū)分萍膛。
  • 缺少this吭服、super、new和顯式泛型調用蝗罗。
  • 增加Null合并操作android:text="@{user.displayName ?? user.lastName}"艇棕,相當于android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • 獲取對象的屬性值串塑,只需要.就夠了沼琉。雖然Data Binding支持數(shù)據(jù)模型以公開的成員變量、getters和ObservableFields三種方法對外提供數(shù)據(jù)(后面數(shù)據(jù)對象中會詳細講)桩匪,但框架能自動判斷并識別打瘪。
  • 非空處理與<variable>的方法保持一樣。
  • 常用的集合:arrays傻昙、lists闺骚、sparse lists和maps,都可以使用[...]來訪問妆档,只不過前三個是下標僻爽,最后一個是key。
  • 由于屬性值通常用雙引號包裹过吻,所以字符串可以使用單引號进泼,或者屬性值用單引號包裹,字符串用雙引號纤虽。
  • 按照表達式的規(guī)則使用Android資源乳绕。對于strings和plurals兩種類型,格式化也是可以正常使用的逼纸。不熟悉plurals可以看看這篇文章洋措。
// 加減乘除等也是可以的
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
// 對字符串進行格式化
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

5.事件處理


事件處理都是基于回調函數(shù)的,Data Binding內置線程安全機制杰刽。由于是在XML中設置回調函數(shù)的邏輯表達式菠发,所以屬性名為對應事件監(jiān)聽器的方法名王滤。

這里需要注意區(qū)分監(jiān)聽器方法和邏輯表達式,前者才是直接被調用的滓鸠,后者是內部的實現(xiàn)雁乡。

根據(jù)監(jiān)聽器創(chuàng)建的時間,將設置方式分為兩種:

  • 編譯時創(chuàng)建糜俗,稱為方法引用踱稍。從寫法上可以理解為用自己的方法替換事件監(jiān)聽器的方法,所以自己方法的參數(shù)與事件監(jiān)聽器方法的參數(shù)一致悠抹。
public class MyHandlers {
    // 與OnClickListener的onClick(View)方法一致
    public void onClickFriend(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </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:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>
  • 觸發(fā)時創(chuàng)建珠月,稱為監(jiān)聽器綁定。寫法上來說楔敌,先用lambda表達式構造能替換事件監(jiān)聽器方法的結構(要么忽略所有的參數(shù)啤挎,要么保留所有的參數(shù)),再用正常的表達式調用自己寫好的方法(只要求返回值與監(jiān)聽器方法的一致卵凑,當監(jiān)聽器方法返回void時庆聘,自己方法可以隨意)
public class MyHandlers {
    public void onClickFriend(Task task){}
}
// 可以用這個替換
android:onClick="@{() -> handlers.onClickFriend(task)}"

6.數(shù)據(jù)對象


XML中使用的數(shù)據(jù)對象是由Java類聲明模型,實例化創(chuàng)建數(shù)據(jù)得出的氛谜。根據(jù)對外公開數(shù)據(jù)的方式分為三種類型掏觉,前兩種是基礎但不支持數(shù)據(jù)綁定,最后這種類型才是Data Binding推薦使用的值漫。

6.1.POJO

使用公開的成員變量來給外界提供訪問澳腹。

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}
6.2.JavaBeans

貫徹面向對象的原則,私有化成員變量杨何,并提供公開的getters和setters方法來讓外界訪問酱塔。

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}
6.3.Observable

Data Binding的要求是當數(shù)據(jù)變化時,可以通知外界危虱,數(shù)據(jù)已經(jīng)變化了羊娃。為此,提供了Observable接口埃跷,實現(xiàn)的類可以為對象添加一個監(jiān)聽器蕊玷,來監(jiān)聽所有屬性的變化。但是通知需開發(fā)人員實現(xiàn)弥雹,這是一個通用的邏輯垃帅,所以又提供了封裝Observable接口邏輯的類和變量。

  • BaseObservable
    將數(shù)據(jù)模型繼承BaseObservable類剪勿,為每個getter方法添加@Bindable注解贸诚。這樣編譯時,會在模塊包目錄下自動生成BR類文件,里面含有被注解的實體酱固。由于基類是不具備通知能力的械念,所以需在setter方法中調用notifyPropertyChanged()方法來通知監(jiān)聽器操作。
private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getFirstName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyChange(); // 通知改變了所有屬性
   }
}
  • ObservableFields
    當不需要對一個類進行監(jiān)聽時运悲,可以使用ObservableField<T>作為屬性龄减,對添加的類型進行監(jiān)聽。若添加的是基本類型扇苞,直接使用已經(jīng)封裝好的欺殿,例如ObservableBoolean寄纵,ObservableByte鳖敷,ObservableChar,ObservableShort程拭,ObservableInt定踱,ObservableLong,ObservableFloat恃鞋,ObservableDouble和ObservableParcelable崖媚。同理,集合也有專門的封裝類ObservableArrayMap和ObservableArrayList恤浪。
private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}
// 給對象屬性賦值和取值時
user.firstName.set("Google");
int age = user.age.get();

7.Binding對象


Binding類將根據(jù)layout文件自動生成畅哑,但是使用時需要其對象,而傳統(tǒng)開發(fā)中水由,把layout文件生成View或其子對象來使用荠呐。由此可以推測,Binding對象是對View的封裝砂客,來代替View對象泥张。傳統(tǒng)開發(fā)中Activity的setContentView()方法,LayoutInflater的inflate()方法以及View的findViewById()方法等在Binding中都有相應的方法鞠值。

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
// 通常用于ListView或RecyclerVIew
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);
// 不知道Binding類名時
MyLayoutBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.my_layout, viewGroup, false);
// 存在根視圖對象時
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
// 不知道Binding類名且存在根視圖對象時
MyLayoutBinding binding = DataBindingUtil.bindTo(viewRoot, R.layout.my_layout);
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </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:text="@{user.firstName}"
           android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:id="@+id/lastName"/>
   </LinearLayout>
</layout>
// layout文件中的帶id的視圖媚创,將會在Binding對象中生成public final屬性
public final TextView firstName;
public final TextView lastName;
// 通過 . 的方式調用
binding.firstName.setText();
binding.lastName.setText();

Binding類為XML中所有<variable>生成setters和getters,再通過其實例調用彤恶,就可以實現(xiàn)與布局傳輸數(shù)據(jù)钞钙。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

8.總結


掌握以上知識點,對于固定界面和基本控件的使用就沒有問題了声离,但Data Binding的作用不止這些芒炼。舉幾個例子大家思考一下,想復用某個界面抵恋,那如何給這個公共界面?zhèn)髦祷酪椋粚τ诿總€子項layout都不一樣的列表控件,該如何賦值顯示等,這些高級的用法會在下一講中說明盅安。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末唤锉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子别瞭,更是在濱河造成了極大的恐慌窿祥,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝙寨,死亡現(xiàn)場離奇詭異晒衩,居然都是意外死亡,警方通過查閱死者的電腦和手機墙歪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門听系,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虹菲,你說我怎么就攤上這事靠胜。” “怎么了毕源?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵浪漠,是天一觀的道長。 經(jīng)常有香客問我霎褐,道長址愿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任冻璃,我火速辦了婚禮响谓,結果婚禮上,老公的妹妹穿的比我還像新娘俱饿。我一直安慰自己歌粥,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布拍埠。 她就那樣靜靜地躺著失驶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枣购。 梳的紋絲不亂的頭發(fā)上嬉探,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音棉圈,去河邊找鬼涩堤。 笑死,一個胖子當著我的面吹牛分瘾,可吹牛的內容都是我干的胎围。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼白魂!你這毒婦竟也來了汽纤?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤福荸,失蹤者是張志新(化名)和其女友劉穎蕴坪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敬锐,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡背传,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了台夺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片径玖。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖谒养,靈堂內的尸體忽然破棺而出挺狰,到底是詐尸還是另有隱情,我是刑警寧澤买窟,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站薯定,受9級特大地震影響始绍,放射性物質發(fā)生泄漏。R本人自食惡果不足惜话侄,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一亏推、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧年堆,春花似錦吞杭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痒蓬,卻和暖如春童擎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背攻晒。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工顾复, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鲁捏。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓芯砸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子假丧,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內容