Android官方數(shù)據(jù)綁定框架DataBinding

Android推出了一個(gè)官方的數(shù)據(jù)綁定框架-Data Binding Library。我們就來(lái)了解一下android最新給我們帶來(lái)的數(shù)據(jù)綁定框架Data Binding Library。數(shù)據(jù)綁定框架給我們帶來(lái)了更大的方便性,以前我們可能需要在Activity里寫(xiě)很多的findViewById,煩人的代碼也增加了我們代碼的耦合性,現(xiàn)在我們馬上就可以拋棄那么多的findViewById。說(shuō)到這里,有人可能會(huì)有個(gè)疑問(wèn):我使用一些注解框架也可以不用findViewById 啊,是的,但是注解注定要拖慢我們代碼的速度,Data Binding則不會(huì),官網(wǎng)文檔說(shuō)還會(huì)提高解析XML的速度,最主要的Data Binding并不是單單減少了我們的findViewById。在代碼開(kāi)始,我們并不直接進(jìn)入新東西的講解,而且以一段代碼展現(xiàn)Data Binding的魅力。
首先我們需要一個(gè)java bean,很簡(jiǎn)單,一個(gè)學(xué)生類(lèi)。

public class Student {
 private String name;
 private String addr;

public Student() {
}

public Student(String name, String addr) {
    this.name = name;
    this.addr = addr;
}

public String getName() {
    return name;
}

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

public String getAddr() {
    return this.addr;
}

public void setAddr(String addr) {
    this.addr = addr;
}
}

再來(lái)看看我們布局文件怎么寫(xiě):

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable
        name="stu"
        type="org.loader.androiddatabinding.Student" />
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{stu.name}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{stu.addr}"/>
</LinearLayout>
</layout>

可以看到我們的xml布局和以前還有有一定的差別的,但是差別也不是很大。 最后來(lái)看看Activity
怎么寫(xiě)。

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    binding.setStu(new Student("loader", "山東萊蕪"));
}
}

Activity的代碼非常簡(jiǎn)單,就添加了兩行代碼,而且,值得注意的是:我們并沒(méi)有findViewById然后再去setText。 這段小代碼運(yùn)行的結(jié)果大家可能已經(jīng)猜到了,就是在界面上顯示loader和山東萊蕪兩句話。



在看完小實(shí)例后,大家是不是感覺(jué)棒棒噠? 沒(méi)有了之前的find控件,沒(méi)有了setText,Activity代碼更加簡(jiǎn)潔明了!
下面開(kāi)始,我們進(jìn)入Data Binding的學(xué)習(xí)!
一、 初始Data Binding
上面的代碼算是帶領(lǐng)我們進(jìn)入了Data Binding的世界,那我們先從布局文件開(kāi)始入手Data Binding吧。再來(lái)看看上面的布局文件。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <variable
        name="stu"
        type="org.loader.androiddatabinding.Student" />
</data>
 ...
</layout>

我們的根節(jié)點(diǎn)變成了layout,在layout的子節(jié)點(diǎn)中分成兩部分,第一部分是data節(jié)點(diǎn),第二部分才是我們之前的根節(jié)點(diǎn),在data節(jié)點(diǎn)下我們又定義了一個(gè)variable, 從名稱(chēng)上看,這應(yīng)該是一個(gè)變量,變量的名稱(chēng)是stu,類(lèi)型是org.loader.androiddatabinding.Student,這類(lèi)似我們?cè)趈ava文件中這么定義:

org.loader.androiddatabinding.Student stu;

ok,這樣很好理解了吧,不過(guò)這里要寫(xiě)Student完整的包名,一個(gè)還好,如果這里我們需要多個(gè)Student呢?要累死? NO,NO,NO,我們還可以向?qū)慾ava文件那樣導(dǎo)入包。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <import type="org.loader.app2.Student" />
    <variable
        name="stu"
        type="Student" />
</data>
...
</layout>

這樣寫(xiě),就類(lèi)似于java的

 import org.loader.app2.Student;
 ...
 Student stu;
 ...

既然變量我們定義好了,那該怎么使用呢?還是看上面的xml文件。

 <layout xmlns:android="http://schemas.android.com/apk/res/android">
  ...
  <LinearLayout
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">
  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{stu.name}"/>

  <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{stu.addr}"/>
 </LinearLayout>
</layout>

恩,注意看兩個(gè)TextView的android:text,它的值是一個(gè)以@開(kāi)始,以{}包裹的形式出現(xiàn),而內(nèi)容呢?是stu.name。stu就是我們上面定義的variable,name還記得嗎?是我們Student類(lèi)中的一個(gè)變量。其實(shí)這里就會(huì)去調(diào)用stu.getName()方法。 好了,很快,我們就入門(mén)了Data Binding,下面讓我們來(lái)多定義幾個(gè)變量試試看。

 <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <import type="org.loader.app2.Student" />
    <variable
        name="stu"
        type="Student" />
    <variable
        name="str"
        type="String"/>
    <variable
        name="error"
        type="boolean"/>
    <variable
        name="num"
        type="int" />
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{stu.name}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{str}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(num)}"/>
</LinearLayout>
</layout>

來(lái)看看定義的變量,多了好幾個(gè),有一個(gè)String類(lèi)型的變量我們并沒(méi)有導(dǎo)包,這里說(shuō)明一下,和在java里一樣,java.lang包里的類(lèi),我們是可以不用導(dǎo)包的,再往下,一個(gè)boolean和int類(lèi)型的變量,都是java基本類(lèi)型的,所以說(shuō)嘛,在這里定義變量,你就想成是在java里定義就ok。 再來(lái)看看這幾個(gè)TextView,第二個(gè),我們直接使用@{str}來(lái)為android:text設(shè)置成上面定義個(gè)str的值,繼續(xù)往下要注意了,我們使用了

android:text="@{String.valueOf(num)}"

來(lái)設(shè)置了一個(gè)int類(lèi)型的變量,大家都知道我們?cè)诮oandroid:text設(shè)置int類(lèi)型的值時(shí)一定要轉(zhuǎn)化為String類(lèi)型,要不它就認(rèn)為是資源文件了,這里我們還學(xué)到了一點(diǎn),在xml中,我們不僅可以使用變量,而且還可以調(diào)用法!
二、 變量定義的高級(jí)部分
在上面,我們學(xué)會(huì)了如何去在xml中定義變量,但是不知道你發(fā)現(xiàn)沒(méi)?我們沒(méi)有定義像List、Map等這樣的集合變量。那到底能不能定義呢?答案肯定是可以的,而且定義的方式和我們上面的基本一致,區(qū)別就在于我們還需要為它定義key的變量,例如:

 <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <import type="org.loader.app2.Student" />
    <import type="android.graphics.Bitmap" />
    <import type="java.util.ArrayList" />
    <import type="java.util.HashMap" />
    <variable
        name="stu"
        type="Student" />
    <variable
        name="str"
        type="String"/>
    <variable
        name="error"
        type="boolean"/>
    <variable
        name="num"
        type="int" />
    <variable
        name="list"
        type="ArrayList<String>" />
    <variable
        name="map"
        type="HashMap<String, String>" />
    <variable
        name="array"
        type="String[]" />

    <variable
        name="listKey"
        type="int" />
    <variable
        name="mapKey"
        type="String" />
    <variable
        name="arrayKey"
        type="int" />
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{stu.name}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{str}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(num)}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{list[listKey]}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{map[`name`]}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{array[0]}"/>
</LinearLayout>
</layout>

這段代碼比較長(zhǎng),但是我們僅關(guān)心那幾個(gè)集合和數(shù)組,可以看到我們定義集合和定義普通變量一樣,只不過(guò)這里我們還指定了一些的泛型,例如: ArrayList<String>。
下面我們還為下面使用這些集合準(zhǔn)備了幾個(gè)key,也都是變量。 繼續(xù)看看怎么使用,和我們?cè)趈ava中使用不同,這里都是以:集合變量名[key]的形式使用,如果你的key是一個(gè)字面字符串可以使用反引號(hào),也可以使用轉(zhuǎn)義后的雙引號(hào)。恩,這里也沒(méi)有什么可以說(shuō)的了,大家多看幾遍就掌握了,都是概念性的東西,記住就ok。
三、在java代碼中使用 前面定義了這么多變量,但是我們還沒(méi)有給他們賦值!在哪賦值呢?肯定是在java代碼中使用了,大部分情況我們還是在Activity中去使用它,以前我們都是在onCreate方法中通setContentView
去設(shè)置布局,但現(xiàn)在不一樣了,現(xiàn)在我們是用過(guò)DataBindingUtil類(lèi)的一個(gè)靜態(tài)方法setContentView設(shè)置布局,同時(shí)該方法會(huì)返回一個(gè)對(duì)象,什么對(duì)象?這個(gè)對(duì)象有點(diǎn)特殊,它是一個(gè)自動(dòng)生成的類(lèi)的對(duì)象,看下面:

@Override
 protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityMainBinding binding = DataBindingUtil.setContentView(this,
           R.layout.activity_main);
}

看到ActivityMainBinding了嗎?就是它!那自動(dòng)生成有什么規(guī)則了沒(méi)?當(dāng)然有了,記好了

 將我們布局文件的首字母大寫(xiě),并且去掉下劃線,將下劃線后面的字母大寫(xiě),加上Binding組成。

看看上面的類(lèi),是不是符合這個(gè)規(guī)則。繼續(xù)看看這個(gè)對(duì)象哪來(lái)的,是通過(guò)

DataBindingUtil.setContentView(this, R.layout.activity_main);

返回的,DataBindingUtil.setContentView的兩個(gè)參數(shù)分別是當(dāng)前Activity和布局文件。那接下來(lái),就是我們關(guān)心的給變量賦值了。

@Override
protected void onCreate(Bundle savedInstanceState) {
...
binding.setStu(new Student("loader"));
binding.setStr("string");
binding.setError(false);

ArrayList<String> list = new ArrayList<String>() {
    {
        add("arraylist");
    }
};
binding.setList(list);
binding.setListKey(0);

HashMap<String, String> map = new HashMap<String, String>() {
    {
        put("name", "hashmap");
    }
};
binding.setMap(map);
//        binding.setMapKey("name");

String[] array = new String[1];
array[0] = "array";
binding.setArray(array);
binding.setArrayKey(0);
}

一連串的binding.setXXX,這個(gè)XXX是什么呢?就是我們?cè)趚ml中定義的那些變量首字母大寫(xiě)了!也沒(méi)好好說(shuō)的吧,多看幾遍。
四、 表達(dá)式 短暫的幸福時(shí)光,我們還是要告別java代碼了,繼續(xù)回到xml中,這一塊,我們來(lái)學(xué)習(xí)一下表達(dá)式,什么?這玩意在xml中還支持表達(dá)式!

 <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{error ? "error" : "ok"}'/>

還記得上面我們定義了一個(gè)boolean的變量沒(méi)有用到,這里我們就用到了,看好android:text
,這里是一個(gè)三元表達(dá)式,如果error是true,則text就是error,否則是ok。這里還支持null合并操作,什么是null合并,相信看一眼你就知道了

  android:text='@{str==null ?? "not null"}'

簡(jiǎn)單解釋一下,如果str是null,text的值就是str本身,否則就是”not null”。 它還支持一下表達(dá)式:
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <=
instanceof
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:

但是它不支持一下表達(dá)式:
this
super
new
Explicit generic invocation

五、 其他遺漏點(diǎn)
說(shuō)到這里,xml中的事情基本算完了,但是還有幾個(gè)小地方?jīng)]有說(shuō),順便說(shuō)一下。

  1. 設(shè)置別名 假如我們import了兩個(gè)相同名稱(chēng)的類(lèi)咋辦?別怕,別名來(lái)拯救你!例如:
<data>
<import type="xxx.Name" alias="MyName">
<import type="xxx.xx.Name">
 </data>
<TextView xxx:@{MyName.getName()}>
<TextView xxx:@{Name.getName()}>

自定義Binding名稱(chēng) 還記得系統(tǒng)為我們生成好的那個(gè)binding類(lèi)名嗎?如果只能使用那樣的是不是有點(diǎn)太暴力了?好在google對(duì)我們還算友好了,允許我們自定義binding名稱(chēng),定制名稱(chēng)也很簡(jiǎn)單,就是給data一個(gè)class字段就ok。 例如:

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

那么:DataBindingUtils.setContentView返回的binding類(lèi)就是:你的應(yīng)用包名.Custom。
六、事件綁定
大家都知道,在xml中我們可以給button設(shè)置一個(gè)onClick來(lái)達(dá)到事件的綁定,現(xiàn)在DataBinding也提供了事件綁定,而且不僅僅是button。 來(lái)看一下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
    <import type="org.loader.app3.EventHandlers" />
    <variable
        name="handlers"
        type="EventHandlers" />
</data>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CLICK ME"
        android:onClick="@{handlers.handleClick}"/>
</LinearLayout>
</layout>

定義了一個(gè)EventHandlers類(lèi)型的handlers變量,并在onClick的時(shí)候執(zhí)行EventHandlers的handleClick方法。 繼續(xù)看看EventHandlers是怎么寫(xiě)的。

public class EventHandlers {
public void handleClick(View view) {
    Toast.makeText(view.getContext(), "you clicked the view", Toast.LENGTH_LONG).show();
}
}

很簡(jiǎn)單,就是簡(jiǎn)單的Toast了一下,這里要注意的是,handlerClick方法需要一個(gè)View的參數(shù)。

七、 數(shù)據(jù)對(duì)象
我們學(xué)會(huì)了通過(guò)binding為我們的變量設(shè)置數(shù)據(jù),但是不知道你有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題,當(dāng)我們數(shù)據(jù)改變的時(shí)候會(huì)怎樣?數(shù)據(jù)是跟隨著改變呢?還是原來(lái)的數(shù)據(jù)呢?這里告訴你答案:很不幸,顯示的還是原來(lái)的數(shù)據(jù)?那有沒(méi)有辦法讓數(shù)據(jù)源發(fā)生變化后顯示的數(shù)據(jù)也隨之發(fā)生變化?先來(lái)想想ListView是怎么做的, ListView的數(shù)據(jù)是通過(guò)Adapter提供的,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),我們通過(guò)notifyDatasetChanged通過(guò)UI去改變數(shù)據(jù),這里面的原理其實(shí)就是內(nèi)容觀察者,慶幸的是DataBinding也支持內(nèi)容觀察者,而且使用起來(lái)也相當(dāng)方便!
BaseObservable
我們可以通過(guò)Observable的方式去通知UI數(shù)據(jù)已經(jīng)改變了,當(dāng)然了,官方為我們提供了更加簡(jiǎn)便的方式BaseObservable,我們的實(shí)體類(lèi)只需要繼承該類(lèi),稍做幾個(gè)操作,就能輕松實(shí)現(xiàn)數(shù)據(jù)變化的通知。如何使用呢? 首先我們的實(shí)體類(lèi)要繼承BaseObservale類(lèi),第二步在Getter上使用注解@Bindable,第三步,在Setter里調(diào)用方法notifyPropertyChanged,第四步,完成。就是這么簡(jiǎn)單,下面我們來(lái)實(shí)際操作一下。 首先定義一個(gè)實(shí)體類(lèi),并繼承BaseObservable

public class Student extends BaseObservable {
private String name;

public Student() {
}

public Student(String name) {
    this.name = name;
}

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

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

觀察getName方法,我們使用了@Bindable注解,觀察setName,我們調(diào)用了notifyPropertyChanged方法,這個(gè)方法還需要一個(gè)參數(shù),這里參數(shù)類(lèi)似于R.java,保存了我們所有變量的引用地址,這里我們使用了name。
再來(lái)看看布局文件。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
    <import type="org.loader.app4.Student" />
    <variable
        name="stu"
        type="Student"/>
    <variable
        name="click"
        type="org.loader.app4.MainActivity" />
</data>
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{click.click}"
    android:text="@{stu.name}"/>
</layout>

不多說(shuō)了,我們給TextView設(shè)置了文本,還有點(diǎn)擊事件。Activity,

public class MainActivity extends AppCompatActivity {

private Student mStu;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    org.loader.app4.Custom binding = DataBindingUtil.setContentView(this,
            R.layout.activity_main);
    mStu = new Student("loader");
    binding.setStu(mStu);
    binding.setClick(this);
}

public void click(View view) {
    mStu.setName("qibin");
}
}

這段代碼,首先顯示的是loader,當(dāng)我們點(diǎn)擊TextView時(shí),界面換成qibin。
ObservableFields家族
上面使用BaseObservable已經(jīng)非常容易了,但是google工程師還不滿(mǎn)足,繼續(xù)給我們封裝了一系列的ObservableFields,這里有ObservableField,ObservableBoolean,ObservableByte,ObservableChar
,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable
ObservableFields的使用方法就更加簡(jiǎn)單了,例如下面代碼,

public class People {
public ObservableField<String> name = new ObservableField<>();
public ObservableInt age = new ObservableInt();
public ObservableBoolean isMan = new ObservableBoolean();
}

很簡(jiǎn)單,只有三個(gè)ObservableField變量,并且沒(méi)有g(shù)etter和setter,因?yàn)槲覀儾恍枰猤etter和setter。 在xml中怎么使用呢?

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">

    <variable
        name="people"
        type="org.loader.app4.People" />
</data>
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{people.name}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(people.age)}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text='@{people.isMan ? "man" : "women"}'/>
</LinearLayout>
</layout>

也很簡(jiǎn)單,直接使用變量,那怎么賦值和取值呢?這些ObservableField都會(huì)有一對(duì)get和set方法,所以使用起來(lái)也很方便了:

...
mPeople = new People();
binding.setPeople(mPeople);
mPeople.name.set("people");
mPeople.age.set(19);
mPeople.isMan.set(true);
...

也不多說(shuō)了。
Observable Collections
既然普通的變量我們有了ObservableFields的分裝,那集合呢?當(dāng)然也有啦,來(lái)看著兩個(gè)ObservableArrayMap,ObservableArrayList。使用和普通的Map、List基本相同,直接看代碼:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
    <variable
        name="map"
        type="android.databinding.ObservableArrayMap<String,String>" />
    <variable
        name="list"
        type="android.databinding.ObservableArrayList<String>" />
</data>
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{map[`name`]}"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{list[0]}"/>
</LinearLayout>
</layout>

在布局中,使用方式和普通的集合一樣,如果看不太懂,可以往上翻博客,看上面的集合是怎么使用的。 在來(lái)看java文件,怎么設(shè)置數(shù)據(jù),

 ObservableArrayMap<String, String> map = new ObservableArrayMap<>();
 ObservableArrayList<String> list = new ObservableArrayList<>();
 map.put("name", "loader or qibin");
 list.add("loader!!!");
 binding.setMap(map);
 binding.setList(list);

哦,太簡(jiǎn)單了,簡(jiǎn)直和List、Map使用方法一模一樣?。。?br> 八、inflate
不知道大家注意沒(méi)有,上面的代碼我們都是在activity中通過(guò)DataBindingUtil.setContentView來(lái)加載的布局的,現(xiàn)在有個(gè)問(wèn)題了,如果我們是在Fragment中使用呢?Fragment沒(méi)有setContentView怎么辦?不要著急,Data Binding也提供了inflate的支持! 使用方法如下,大家肯定會(huì)覺(jué)得非常眼熟。

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

接下來(lái),我們就嘗試著在Fragment中使用一下Data Binding吧。 首先還是那個(gè)學(xué)生類(lèi),Student

public class Student extends BaseObservable {
private String name;
private int age;

public Student() {
}

public Student(int age, String name) {
    this.age = age;
    this.name = name;
}

@Bindable
public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
    notifyPropertyChanged(org.loader.app5.BR.age);
}

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

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

這里面代碼如果看不懂了,請(qǐng)翻看前一篇博客。 繼續(xù),activity的布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<FrameLayout
    android:id="@+id/container"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

</RelativeLayout>

activity的代碼,

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.container, new MyFragment()).commit();
}
}

重點(diǎn)來(lái)了,我們這里data binding的操作都放在了fragment里,那么我們先來(lái)看看fragment的布局。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
    <import type="org.loader.app5.Student" />
    <variable
        name="stu"
        type="Student" />
    <variable
        name="frag"
        type="org.loader.app5.MyFragment" />
</data>

<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{frag.click}"
        android:text="@{stu.name}"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(stu.age)}"/>
</LinearLayout>
</layout>

兩個(gè)TextView分別綁定了Student的name和age字段,而且給name添加了一個(gè)點(diǎn)擊事件,點(diǎn)擊后會(huì)調(diào)用Fragment的click方法。我們來(lái)迫不及待的看一下Fragment怎么寫(xiě):

public class MyFragment extends Fragment {

private Student mStu;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    org.loader.app5.Custom binding = DataBindingUtil.inflate(inflater,
            R.layout.frag_layout, container, false);
    mStu = new Student(20, "loader");
    binding.setStu(mStu);
    binding.setFrag(this);
    return binding.getRoot();
}

public void click(View view) {
    mStu.setName("qibin");
    mStu.setAge(18);
}
}

在onCreateView中,不同于在Activity中,這里我們使用了DataBindingUtil.inflate方法,接受4個(gè)參數(shù),第一個(gè)參數(shù)是一個(gè)LayoutInflater對(duì)象,正好,我們這里可以使用onCreateView的第一個(gè)參數(shù),第二個(gè)參數(shù)是我們的布局文件,第三個(gè)參數(shù)是一個(gè)ViewGroup,第四個(gè)參數(shù)是一個(gè)boolean類(lèi)型的,和在LayoutInflater.inflate一樣,后兩個(gè)參數(shù)決定了是否想container中添加我們加載進(jìn)來(lái)的布局。
下面的代碼和我們之前寫(xiě)的并無(wú)差別,但是有一點(diǎn),onCreateView方法需要返回一個(gè)View對(duì)象,我們從哪獲取呢?ViewDataBinding有一個(gè)方法getRoot可以獲取我們加載的布局,是不是很簡(jiǎn)單? 來(lái)看一下效果:



九、 Data Binding VS RecyclerView
有了上面的思路,大家是不是也會(huì)在ListView和RecyclerView中使用了?我們僅以一個(gè)RecyclerView來(lái)學(xué)習(xí)一下。 首先來(lái)看看item的布局,

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

<data>
    <variable
        name="stu"
        type="org.loader.app6.Student" />
</data>

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{stu.name}"
        android:layout_alignParentLeft="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(stu.age)}"
        android:layout_alignParentRight="true"/>

</RelativeLayout>
</layout>

可以看到,還是用了那個(gè)Student實(shí)體,這樣得代碼,相信你也已經(jīng)看煩了吧。 那我們來(lái)看看activity的。

private RecyclerView mRecyclerView;
private ArrayList<Student> mData = new ArrayList<Student>() {
 {
    for (int i=0;i<10;i++) add(new Student("loader" + i, 18 + i));
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

mRecyclerView = (RecyclerView) findViewById(R.id.recycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this,
        LinearLayoutManager.VERTICAL, false));
mRecyclerView.setAdapter(new MyAdapter(mData));
}

這里給RecyclerView設(shè)置了一個(gè)Adapter,相信最主要的代碼就在這個(gè)Adapter里。

private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

private ArrayList<Student> mData = new ArrayList<>();

private MyAdapter(ArrayList<Student> data) {
    mData.addAll(data);
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater
            .from(viewGroup.getContext()), R.layout.item, viewGroup, false);
    ViewHolder holder = new ViewHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
}

@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
    viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
    viewHolder.getBinding().executePendingBindings();
}

@Override
public int getItemCount() {
    return mData.size();
}

class ViewHolder extends RecyclerView.ViewHolder {

    private ViewDataBinding binding;

    public ViewHolder(View itemView) {
        super(itemView);
    }

    public void setBinding(ViewDataBinding binding) {
        this.binding = binding;
    }

    public ViewDataBinding getBinding() {
        return this.binding;
    }
}
}

果然,這個(gè)adapter的寫(xiě)法和我們之前的寫(xiě)法不太一樣,首先看看ViewHolder,在這個(gè)holder里,我們保存了一個(gè)ViewDataBinding對(duì)象,并給它提供了Getter和Setter方法, 這個(gè)ViewDataBinding是干嘛的?我們稍后去講。繼續(xù)看看onCreateViewHolder,在這里面,我們首先調(diào)用DataBindingUtil.inflate方法返回了一ViewDataBinding的對(duì)象,這個(gè)ViewDataBinding是個(gè)啥?我們以前沒(méi)見(jiàn)過(guò)啊,這里告訴大家我們之前返回的那些都是ViewDataBinding的子類(lèi)!繼續(xù)看代碼,我們new了一個(gè)holder,參數(shù)是肯定是我們的item布局了,繼續(xù)看,接著我們又把binding設(shè)置給了holder,最后返回holder。這時(shí)候,我們的holder里就保存了剛剛返回的ViewDataBinding對(duì)象,干嘛用呢?繼續(xù)看onBindViewHolder就知道了。

 @Override
 public void onBindViewHolder(ViewHolder viewHolder, int i) {
 viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));
 viewHolder.getBinding().executePendingBindings();
}

只有兩行代碼,但是都是我們沒(méi)有見(jiàn)過(guò)的,首先第一行,我們以前都是使用類(lèi)似binding.setStu
這樣方法去設(shè)置變量,那這個(gè)setVariable呢? 為什么沒(méi)有setStu,這里要記住,ViewDataBinding
是我們之前用的那些binding的父類(lèi),只有自動(dòng)生成的那些子類(lèi)才會(huì)有setXXX方法,那現(xiàn)在我們需要在ViewDataBinding中設(shè)置變量咋辦?這個(gè)類(lèi)為我們提供了setVariable去設(shè)置變量,第一個(gè)參數(shù)是我們的變量名的引用,第二個(gè)是我們要設(shè)置的值。第二行代碼,executePendingBindings的作用是干嘛的?官方的回答是:

當(dāng)數(shù)據(jù)改變時(shí),binding會(huì)在下一幀去改變數(shù)據(jù),如果我們需要立即改變,就去調(diào)executePendingBindings方法。

所以這里的作用就是去讓數(shù)據(jù)的改變立即執(zhí)行。 ok,現(xiàn)在看起來(lái),我們的代碼更加簡(jiǎn)潔了,而且不需要保存控件的實(shí)例,是不是很爽? 來(lái)看看效果:



十、 View with ID
在使用Data Binding的過(guò)程中,我們發(fā)現(xiàn)并沒(méi)有保存View的實(shí)例,但是現(xiàn)在我們有需求需要這個(gè)View的實(shí)例咋辦?難道走老路findViewById?當(dāng)然不是啦,當(dāng)我們需要某個(gè)view的實(shí)例時(shí),我們只要給該view一個(gè)id,然后Data Binding框架就會(huì)給我們自動(dòng)生成該view的實(shí)例,放哪了?當(dāng)然是ViewDataBinding里面。 上代碼:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data class=".Custom">
    <variable
        name="str"
        type="android.databinding.ObservableField<String>" />
    <variable
        name="handler"
        type="org.loader.app7.MainActivity" />
</data>

<TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{str.get}"
    android:onClick="@{handler.click}"/>
</layout>

xml中代碼沒(méi)有什么好說(shuō)的,都是之前的代碼,如果在這有點(diǎn)迷糊,建議你還是回頭看看上篇博客。需要注意的是, 我們給TextView了一個(gè)id-textView。
activity,

public class MainActivity extends AppCompatActivity {

private org.loader.app7.Custom mBinding;
private ObservableField<String> mString;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this,
            R.layout.activity_main);
    mString = new ObservableField<String>();
    mString.set("loader");
    mBinding.setStr(mString);
    mBinding.setHandler(this);
}

public void click(View view) {
    mString.set("qibin");
    mBinding.textView.setTextColor(Color.GREEN);
}
}

主要還是來(lái)看click方法中,這里我們需要獲取TextView的實(shí)例,用來(lái)改變他的顏色,我們是通過(guò)ViewDataBinding類(lèi)的實(shí)例直接去獲取的。只要我們給了view一個(gè)id,那么框架就會(huì)在ViewDataBinding中自動(dòng)幫我們保存這個(gè)view的實(shí)例,變量名就是我們?cè)O(shè)置的id。

十一、 自定義setter
想想這樣的一種情景,一個(gè)ImageView需要通過(guò)網(wǎng)絡(luò)去加載圖片,那我們?cè)趺崔k?看似好像使用DataBinding不行,恩,我們上面所學(xué)到東西確實(shí)不能夠解決這個(gè)問(wèn)題,但是DataBinding框架給我們提供了很好的擴(kuò)展,允許我們自定義setter,那該怎么做呢?這里就要引出另一個(gè)知識(shí)點(diǎn)——BindingAdapter,這是一個(gè)注解,參數(shù)是一個(gè)數(shù)組,數(shù)組中存放的是我們自定義的’屬性’。接下來(lái)就以一個(gè)例子學(xué)習(xí)一下BindingAdapter的使用。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data class=".Custom">
    <variable
        name="imageUrl"
        type="String" />
</data>

<ImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:image="@{imageUrl}"/>
</layout>

這里我們?cè)黾恿艘粋€(gè)命名空間app,并且注意ImageView的app:image屬性,這里和我們自定義view時(shí)自定義的屬性一樣,但是這里并不需要我們?nèi)ブ貙?xiě)ImageView,這條屬性的值是我們上面定義的String類(lèi)型的imageUrl,從名稱(chēng)中看到這里我們可能會(huì)塞給他一個(gè)url。
activity,

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    org.loader.app8.Custom binding = DataBindingUtil.setContentView(this,
            R.layout.activity_main);
    binding.setImageUrl("http://images.csdn.net/20150810/Blog-Image%E5%89%AF%E6%9C%AC.jpg");
}
}

果然在這里我們set了一個(gè)url,那圖片怎么加載呢?這里就要使用到我們剛才說(shuō)的BindingAdapter注解了。

public class Utils {
@BindingAdapter({"bind:image"})
public static void imageLoader(ImageView imageView, String url) {
    ImageLoaderUtils.getInstance().displayImage(url, imageView);
}
}

我們定義了一個(gè)Utils類(lèi),這個(gè)類(lèi)你可以隨便起名,該類(lèi)中只有一個(gè)靜態(tài)的方法imageLoader,該方法有兩個(gè)參數(shù),一個(gè)是需要設(shè)置數(shù)據(jù)的view, 一個(gè)是我們需要的url。值得注意的是那個(gè)BindingAdapter注解,看看他的參數(shù),是一個(gè)數(shù)組,內(nèi)容只有一個(gè)bind:image,僅僅幾行代碼,我們不需要 手工調(diào)用Utils.imageLoader,也不需要知道imageLoader方法定義到哪了,一個(gè)網(wǎng)絡(luò)圖片加載就搞定了,是不是很神奇,這里面起關(guān)鍵作用的就是BindingAdapter
注解了,來(lái)看看它的參數(shù)怎么定義的吧,難道是亂寫(xiě)?當(dāng)然不是,這里要遵循一定的規(guī)則,
以bind:開(kāi)頭,接著書(shū)寫(xiě)你在控件中使用的自定義屬性名稱(chēng)。

這里就是image了,不信來(lái)看。

<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:image="@{imageUrl}"/>

看看運(yùn)行結(jié)果:



十二、 Converters
Converter是什么呢?舉個(gè)例子吧:假如你的控件需要一個(gè)格式化好的時(shí)間,但是你只有一個(gè)Date類(lèi)型額變量咋辦?肯定有人會(huì)說(shuō)這個(gè)簡(jiǎn)單,轉(zhuǎn)化完成后在設(shè)置,恩,這也是一種辦法,但是DataBinding還給我們提供了另外一種方式,雖然原理一樣,但是這種方式使用的場(chǎng)景更多,那就是——Converter。和上面的BindingAdapter使用方法一樣,這也是一個(gè)注解。下面還是以一段代碼的形式進(jìn)行學(xué)習(xí)。

        <layout xmlns:android="http://schemas.android.com/apk/res/android">
            <data class=".Custom">
    <variable
        name="time"
        type="java.util.Date" />
       </data>

   <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{time}"/>
     </layout>

看TextView的text屬性,我們需要一個(gè)String類(lèi)型的值,但是這里確給了一個(gè)Date類(lèi)型的,這就需要我們?nèi)ザxConverter去轉(zhuǎn)換它,
activity,

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    org.loader.app9.Custom binding = DataBindingUtil.setContentView(this,
            R.layout.activity_main);
    binding.setTime(new Date());
}
}

去給這個(gè)Date類(lèi)型的變量設(shè)置值。怎么去定義Converter呢? 看代碼:

public class Utils {

@BindingConversion
public static String convertDate(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(date);
}
}

和上面一樣,我們不需要關(guān)心這個(gè)convertDate在哪個(gè)類(lèi)中,重要的是他的@BindingConversion注解,這個(gè)方法接受一個(gè)Date類(lèi)型的變量,正好我們的Android:text設(shè)置的就是一個(gè)Date類(lèi)型的值,在方法內(nèi)部我們將這個(gè)Date類(lèi)型的變量轉(zhuǎn)換成String類(lèi)型的日期并且返回。這樣UI上就顯示出我們轉(zhuǎn)化好的字符串。 看看效果:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容