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

參考自一葉飄舟

Android新推出了一個(gè)官方的數(shù)據(jù)綁定框架Data Binding Library,既然是官方推出的新玩意,我們就有必要了解一下Android新帶來(lái)的數(shù)據(jù)綁定框架,等到該框架推出正式版的時(shí)候,我們就可以快速地運(yùn)用到項(xiàng)目中去。數(shù)據(jù)綁定框架給我們帶來(lái)了很大的方便性,以前我們可能需要在Activity里寫(xiě)很多的findViewById,煩人的代碼也增加了我們代碼的耦合性,現(xiàn)在我們馬上就可以?huà)仐壞切ゝindViewById。說(shuō)到這里,有人可能會(huì)問(wèn):我使用的一些注解框架也可以不用findViewById啊,是的,但是注解的缺點(diǎn)是拖累代碼的效率,Data Binding則不會(huì),Android官方文檔說(shuō)還會(huì)提高解析XML的速度,最主要的是Data Binding并不是單單減少我們的findViewById,更多的好處我們接下來(lái)一起探尋。

1.環(huán)境

使用最新的Android Studio 1.5.1正式版,并更新你的Suport Repository到最新的版本,確保Android Studio的Gradle插件不低于1.5.0

classpath 'com.android.tools.build:gradle:1.5.0'

然后修改對(duì)應(yīng)模塊(Module)的build.gradle,添加如下腳本代碼:

android {
    
    //添加DataBinding Library
    dataBinding {
    enabled true
    }
}

最后,點(diǎn)擊Sync同步一下Gradle即可完成環(huán)境配置

2.Data Binding示例

首先,我們需要新建一個(gè)Java Bean,一個(gè)簡(jiǎn)單的學(xué)生類(lèi)。

```
package com.example.jimi098.databinding;

/**
 * Created by jimi098 on 2016/2/16.
 */
public class Student {

    private String name;
    private String addr;

    public Student() {

    }

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

    public String getName() {
        return name;
    }

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

    public String getAddr() {
        return addr;
    }

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

其次,編寫(xiě)布局文件data_binding.xml:

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

    <data>
        
        <variable
            name="stu"              
            type="com.example.jimi098.databinding.Student" />
    </data>


    <LinearLayout
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{stu.name}"/>  //也可以是android:text="@{stu.getName()}"

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{stu.addr}"/> //也可以是android:text="@{stu.getAddr()}"
    </LinearLayout>

</layout>

```

最后,實(shí)現(xiàn)MainActivity,為變量賦值

```
import com.example.jimi098.databinding.databinding.DataBindingBinding;

public class MainActivity extends AppCompatActivity {


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

        DataBindingBinding binding =  DataBindingUtil.setContentView(this, R.layout.data_binding);
        binding.setStu(new Student("lee", "Shenzhen"));

    }
```

由上面可以看出,MainActivity的代碼非常簡(jiǎn)單,就添加了兩行代碼,需要注意的是我們并沒(méi)有findViewById然后再去setText。

運(yùn)行結(jié)果如下圖所示:

3.Data Binding詳解

上面的示例僅僅是帶領(lǐng)我們進(jìn)入了Data Binding的世界,接下來(lái)我們解釋一下Data Binding的開(kāi)發(fā)步驟。先看看上面的布局文件。

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

    <data>
        
        <variable
            name="stu"              
            type="com.example.jimi098.databinding.Student" />
    </data>


    .....

```

根節(jié)點(diǎn)使用的是layout,在layout中分成兩部分,第一部分是data節(jié)點(diǎn),第二部分才是我們布局的根節(jié)點(diǎn),在data節(jié)點(diǎn)下我們定義了一個(gè)variable,它是一個(gè)變量,變量名稱(chēng)是stu,類(lèi)型是com.example.jimi098.databinding.Student,這類(lèi)似我們?cè)趈ava文件中的定義:

```
com.example.jimi098.databinding.Student stu;
```

不過(guò)這里要寫(xiě)Student完整的包名,如果這里我們需要多個(gè)Student呢?我們可以像寫(xiě)java文件那樣導(dǎo)入類(lèi)包

```
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <data>

        <!--導(dǎo)入類(lèi)包-->
        <import type="com.example.jimi098.databinding.Student"/>
        
        <variable
            name="stu"
            type="Student" />
    </data>

    .....

</layout>
```

這樣就類(lèi)似于java中的

```
import com.example.jimi098.databinding.Student;

Student stu1,stu2,...
```

既然變量定義好了,那該怎么使用呢?我們?nèi)匀豢瓷厦娴膞ml文件

```
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    ....

    <LinearLayout
        android:orientation="vertical">

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

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

</layout>
```

由上面可以看出,兩個(gè)TextView的android:text,它的值是以@開(kāi)始,以{}包裹的形式出現(xiàn),而值呢?是stu.name,stu就是上面定義的variable,name就是Student類(lèi)中的成員變量,其實(shí)這里就會(huì)去調(diào)用stu.getName()方法。

最后,我們看看如何給變量賦值呢?如下代碼:

```
import com.example.jimi098.databinding.databinding.DataBindingBinding;

public class MainActivity extends AppCompatActivity {


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

        DataBindingBinding binding =  DataBindingUtil.setContentView(this, R.layout.data_binding);
        binding.setStu(new Student("lee", "Shenzhen")); //賦值

    }

```

大部分情況,我們會(huì)在Activity中去使用它,以前我們都是在OnCreate方法中通過(guò)setContextView去設(shè)置布局。但現(xiàn)在不一樣了,現(xiàn)在我們是通過(guò)DataBindingUtil類(lèi)的一個(gè)靜態(tài)方法setContentView設(shè)置布局,同時(shí)該方法會(huì)返回一個(gè)對(duì)象,這個(gè)對(duì)象時(shí)一個(gè)自動(dòng)生成的類(lèi)的對(duì)象,如DataBindingBinding?那么它的命名規(guī)則是什么呢?將我們布局文件的首字母大寫(xiě),并且去掉下劃線(xiàn),將下劃線(xiàn)后面的字母大寫(xiě),加上后綴Binding組成。最后,我們通過(guò)這個(gè)對(duì)象來(lái)給變量賦值。

通過(guò)以上分析,我們了解Data Binding的具體開(kāi)發(fā)步驟,下面讓我們定義不同的幾個(gè)變量看看

```
<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <import type="com.example.jimi098.databinding.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>  
```

由上面代碼可以看出,String類(lèi)型的變量沒(méi)有導(dǎo)入包,這是因?yàn)镈ata Binding和Java一樣,java.lang包里的類(lèi),我們是可以不用導(dǎo)入包的,再往下一個(gè)boolean和int類(lèi)型的變量,都是java基本類(lèi)型,也不用導(dǎo)入包。

再來(lái)看看幾個(gè)TextView,第二個(gè)TextView,我們直接使用@{str}來(lái)為android:text設(shè)置文本內(nèi)容;接下來(lái)注意第三個(gè)TextView,我們使用android:text="@{String.valueOf(num)}"來(lái)設(shè)置一個(gè)int類(lèi)型的變量,因?yàn)樵诮oandroid:text設(shè)置int類(lèi)型的值一定要轉(zhuǎn)化為String類(lèi)型,不然系統(tǒng)會(huì)認(rèn)為是資源文件id。此外,我們還學(xué)習(xí)到了一點(diǎn),在Xml中,我們不僅可以使用變量,而且還可以調(diào)用方法

4. 變量定義的高級(jí)部分

在上面,我們學(xué)會(huì)了如何在xml中定義變量,但是我們并沒(méi)有定義像List、Map等這樣的集合變量。那么到底能不能定義呢?答案是肯定的,而且定義的方式和我們上面的基本一致,區(qū)別就在于我們還需要為它定義key的變量,例如:

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

    <data>
        <!--導(dǎo)入類(lèi)包-->
        <import type="com.example.jimi098.databinding.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: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="@{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[mapKey]}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{array[arrayKey]}" />
    </LinearLayout>

</layout>
```

然后在java代碼中為變量賦值

```
public class MainActivity extends AppCompatActivity {


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


        DataBindingBinding binding =  DataBindingUtil.setContentView(this, R.layout.data_binding);
        binding.setStu(new Student("lee", "Shenzhen"));
        binding.setStr("just do it");
        binding.setNum(10);

        ArrayList<String> list = new ArrayList<String>();
        list.add("list1");
        list.add("list2");

        binding.setList(list);
        binding.setListKey(0);

        HashMap<String,String> map = new HashMap<String,String>();
        map.put("name","liu");
        map.put("sex","male");

        binding.setMap(map);
        binding.setMapKey("sex");

        String[] array = new String[2];
        array[0] = "array0";
        array[1] = "array1";

        binding.setArray(array);
        binding.setArrayKey(1);

    }
```

5.表達(dá)式

xml中還支持表達(dá)式

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

如上所示,android:text后是一個(gè)三元表達(dá)式,如果error是true,則text就是error,否則是OK。

除此外還支持null合并操作,??--左邊的對(duì)象如果它不是null,選擇左邊的對(duì)象;或者如果它是null,選擇右邊的對(duì)象

```
<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{str ?? "not null"}' />
```

還支持以下表達(dá)式:

  • 數(shù)學(xué) + - / * %
  • 字符串連接 +
  • 邏輯 && ||
  • 二進(jìn)制 & | ^
  • 一元運(yùn)算 + - ! ~
  • 移位 >> >>> <<
  • 比較 == > < >= <=
  • instanceof
  • 分組 ()
  • null
  • Cast
  • 方法調(diào)用
  • 數(shù)據(jù)訪問(wèn) []
  • 三元運(yùn)算 ?:

示例:

```
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
```

但是它不支持一下表達(dá)式:

  • this
  • super
  • new
  • 顯式泛型調(diào)用

6. 其他知識(shí)點(diǎn)

6.1 設(shè)置別名

假如我們import了兩個(gè)相同名稱(chēng)的類(lèi)咋辦?我們可以借助于別名來(lái)解決,別名借助alias字段來(lái)標(biāo)識(shí),例如:

```
<data>  
  <import type="xxx.Name" alias="MyName">  
  <import type="xxx.xx.Name">  
</data>  
<TextView xxx:@{MyName.getName()}>  
<TextView xxx:@{Name.getName()}>  
```

6.2 自定義Binding類(lèi)名稱(chēng)

默認(rèn)情況下,Binding類(lèi)的命名是基于所述layout文件的名稱(chēng),用大寫(xiě)開(kāi)頭,除去下劃線(xiàn)()以及()后的第一個(gè)字母大寫(xiě),然后添加“Binding”后綴。這個(gè)類(lèi)將被放置在一個(gè)模塊封裝包里的databinding封裝包下。例如,所述layout文件contact_item.xml將生成ContactItemBinding。如果模塊包是com.example.my.app,那么它將被放置在com.example.my.app.databinding。

Binding類(lèi)可通過(guò)調(diào)整data元素中的class屬性來(lái)重命名或放置在不同的包中。例如:

```
<data class="ContactItem">
    ...
</data>
```

在模塊封裝包的databinding包中會(huì)生成名為ContactItem的Binding類(lèi)。如果要想讓該類(lèi)生成在不同的包中,你需要添加前綴.,如下:

```
<data class=".ContactItem">
    ...
</data>
```

在這個(gè)情況下,ContactItem類(lèi)直接在模塊包中生成?;蛘吣憧梢蕴峁┱麄€(gè)包名:

```
<data class="com.example.ContactItem">
    ...
</data>
```

6.3 字符串

當(dāng)使用單引號(hào)包含屬性值時(shí),在表達(dá)式中使用雙引號(hào)很容易:
android:text='@{map["firstName"]}'

使用雙引號(hào)來(lái)包含屬性值也是可以的。字符串前后需要使用"": android:text="@{map[`firstName`]}"

6.4 Resources

使用正常的表達(dá)式來(lái)訪問(wèn)resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

6.5 include

通過(guò)使用application namespace以及在屬性中的Variable名字從容器layout中傳遞Variables到一個(gè)被包含的layout:

```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>
```

** 注意:在name.xml以及contact.xml兩個(gè)layout文件中必需要有user variable**

7.事件綁定

大家都知道,在xml中我們可以給button設(shè)置一個(gè)onClick來(lái)達(dá)到事件的綁定,現(xiàn)在DataBinding也提供了事件綁定,而且不僅僅是button。首先定義一個(gè)對(duì)象處理點(diǎn)擊事件,如下:

```
/**
 * Created by jimi098 on 2016/2/16.
 */
public class EventHandler {

    public void handleClick(View view) {

        Toast.makeText(view.getContext(),"click",Toast.LENGTH_SHORT).show();
    }
}
```

其次看布局:

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

    <data>

        <import type="com.example.jimi098.databinding.EventHandler" />

        <variable
            name="handler"
            type="EventHandler" />
    </data>

    <LinearLayout android:orientation="vertical">
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Click"
            android:onClick="@{handler.handleClick}"/>
    </LinearLayout>

</layout>

```

最后,實(shí)現(xiàn)事件綁定

```
public class MainActivity extends AppCompatActivity {


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

        ClickBindingBinding binding =  DataBindingUtil.setContentView(this, R.layout.click_binding);
        binding.setHandler(new EventHandler());

    }
```

8.Data對(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)方便!

8.1 Observable

我們可以通過(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

```
/**
 * Created by jimi098 on 2016/2/16.
 */
public class Student extends BaseObservable{

    private String name;
    private String addr;

    public Student() {

    }

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

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

    public void setName(String name) {
        this.name = name;
        notifyPropertyChanged(com.example.jimi098.databinding.BR.name);
    }

    @Bindable
    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
        notifyPropertyChanged(com.example.jimi098.databinding.BR.addr);
    }
}
```

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

其次,看看布局文件

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

    <data>

        <import type="com.example.jimi098.databinding.Student" />

        <variable
            name="stu"
            type="Student"/>

        <variable
            name="click"
            type="com.example.jimi098.databinding.MainActivity" />
    </data>

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

</layout>
```

最后,java實(shí)現(xiàn)

```
public class MainActivity extends AppCompatActivity {

    private Student mStu;

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

        ClickBindingBinding binding =  DataBindingUtil.setContentView(this, R.layout.click_binding);
        mStu = new Student("lau","Shenzhen");

        binding.setStu(mStu); //設(shè)置初始顯示數(shù)據(jù)
        binding.setClick(this); //設(shè)置點(diǎn)擊事件

    }


    public void click(View view) {
        //點(diǎn)擊時(shí)數(shù)據(jù)發(fā)生改變
        mStu.setName("lee");
        mStu.setAddr("Beijing");
    }
}
```

8.2 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); 
```

8.3 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);  
```

9.Inflate

上面的代碼我們都是在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);  
    }  
}  
```

其次,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的實(shí)現(xiàn)

```
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)看一下效果:

ALT TEXT

10.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方法返回了一個(gè)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)看看效果:

ALT TEXT

11.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ō)的,都是之前的代碼。需要注意的是,
我們給TextView設(shè)定了一個(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);  //找到控件 
    }  
}  
```

通過(guò)ViewDataBinding類(lèi)的實(shí)例直接去獲取的。只要我們給了view一個(gè)id,那么框架就會(huì)在ViewDataBinding中自動(dòng)幫我們保存這個(gè)view的實(shí)例,變量名就是我們?cè)O(shè)置的id。

12.自定義setter(BindingAdapter)

想想這樣的一種情景,一個(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}"/>  
```

13.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)化好的字符串。
看看效果:

ALT TEXT
最后編輯于
?著作權(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)容