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

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

1.環(huán)境

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

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

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

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

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

2.Data Binding示例

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

package com.example.iaiai.databinding;  
  
/** 
 * Created by iaiai 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;  
    }  
}

其次,編寫布局文件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.iaiai.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.iaiai.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的代碼非常簡單,就添加了兩行代碼,需要注意的是我們并沒有findViewById然后再去setText。

3.Data Binding詳解

上面的示例僅僅是帶領(lǐng)我們進(jìn)入了Data Binding的世界,接下來我們解釋一下Data Binding的開發(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.iaiai.databinding.Student" />  
    </data>  
  
  
    .....

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

com.example.iaiai.databinding.Student stu;

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

<layout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  
  
    <data>  
  
        <!--導(dǎo)入類包-->  
        <import type="com.example.iaiai.databinding.Student"/>  
  
        <variable  
            name="stu"  
            type="Student" />  
    </data>  
  
    .....  
  
</layout>

這樣就類似于java中的

import com.example.iaiai.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,它的值是以@開始,以{}包裹的形式出現(xiàn),而值呢?是stu.name,stu就是上面定義的variable,name就是Student類中的成員變量,其實(shí)這里就會去調(diào)用stu.getName()方法。

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

import com.example.iaiai.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")); //賦值  
  
    }

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

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

<layout xmlns:android="http://schemas.android.com/apk/res/android">    
    <data>    
        <import type="com.example.iaiai.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類型的變量沒有導(dǎo)入包,這是因?yàn)镈ata Binding和Java一樣,java.lang包里的類,我們是可以不用導(dǎo)入包的,再往下一個(gè)boolean和int類型的變量,都是java基本類型,也不用導(dǎo)入包。

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

  1. 變量定義的高級部分

在上面,我們學(xué)會了如何在xml中定義變量,但是我們并沒有定義像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)入類包-->  
        <import type="com.example.iaiai.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合并操作,??--左邊的對象如果它不是null,選擇左邊的對象;或者如果它是null,選擇右邊的對象

<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ù)訪問 []
  • 三元運(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)用
  1. 其他知識點(diǎn)

6.1 設(shè)置別名

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

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

6.2 自定義Binding類名稱

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

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

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

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

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

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

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

6.3 字符串

當(dāng)使用單引號包含屬性值時(shí),在表達(dá)式中使用雙引號很容易:

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

使用雙引號來包含屬性值也是可以的。字符串前后需要使用"":

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

6.4 Resources

使用正常的表達(dá)式來訪問resources也是可行的:

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

6.5 include

通過使用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來達(dá)到事件的綁定,現(xiàn)在DataBinding也提供了事件綁定,而且不僅僅是button。首先定義一個(gè)對象處理點(diǎn)擊事件,如下:

/** 
 * Created by iaiai 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.iaiai.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對象

我們學(xué)會了通過binding為我們的變量設(shè)置數(shù)據(jù),但是不知道你有沒有發(fā)現(xiàn)一個(gè)問題,當(dāng)我們數(shù)據(jù)改變的時(shí)候會怎樣?數(shù)據(jù)是跟隨著改變呢?還是原來的數(shù)據(jù)呢?這里告訴你答案:很不幸,顯示的還是原來的數(shù)據(jù)?那有沒有辦法讓數(shù)據(jù)源發(fā)生變化后顯示的數(shù)據(jù)也隨之發(fā)生變化?先來想想ListView是怎么做的, ListView的數(shù)據(jù)是通過Adapter提供的,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),我們通過notifyDatasetChanged通過UI去改變數(shù)據(jù),這里面的原理其實(shí)就是內(nèi)容觀察者,慶幸的是DataBinding也支持內(nèi)容觀察者,而且使用起來也相當(dāng)方便!

8.1 Observable

我們可以通過Observable的方式去通知UI數(shù)據(jù)已經(jīng)改變了,當(dāng)然了,官方為我們提供了更加簡便的方式BaseObservable,我們的實(shí)體類只需要繼承該類,稍做幾個(gè)操作,就能輕松實(shí)現(xiàn)數(shù)據(jù)變化的通知。如何使用呢? 首先我們的實(shí)體類要繼承BaseObservale類,第二步在Getter上使用注解@Bindable,第三步,在Setter里調(diào)用方法notifyPropertyChanged,第四步,完成。就是這么簡單,下面我們來實(shí)際操作一下。

首先定義一個(gè)實(shí)體類,并繼承BaseObservable

/** 
 * Created by iaiai 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.iaiai.databinding.BR.name);  
    }  
  
    @Bindable  
    public String getAddr() {  
        return addr;  
    }  
  
    public void setAddr(String addr) {  
        this.addr = addr;  
        notifyPropertyChanged(com.example.iaiai.databinding.BR.addr);  
    }  
}

觀察getName方法,我們使用了@Bindable注解,觀察setName,我們調(diào)用了notifyPropertyChanged方法,這個(gè)方法還需要一個(gè)參數(shù),這里參數(shù)類似于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.iaiai.databinding.Student" />  
  
        <variable  
            name="stu"  
            type="Student"/>  
  
        <variable  
            name="click"  
            type="com.example.iaiai.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工程師還不滿足,繼續(xù)給我們封裝了一系列的ObservableFields,這里有ObservableField,ObservableBoolean,ObservableByte,ObservableChar,ObservableShort,ObservableInt,ObservableLong,ObservableFloat,ObservableDouble,ObservableParcelable

ObservableFields的使用方法就更加簡單了,例如下面代碼:

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

很簡單,只有三個(gè)ObservableField變量,并且沒有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>

也很簡單,直接使用變量,那怎么賦值和取值呢?這些ObservableField都會有一對get和set方法,所以使用起來也很方便了:

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)然也有啦,來看著兩個(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>

在來看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中通過DataBindingUtil.setContentView來加載的布局的,現(xiàn)在有個(gè)問題了,如果我們是在Fragment中使用呢?Fragment沒有setContentView怎么辦?不要著急,Data Binding也提供了inflate的支持!

使用方法如下,大家肯定會覺得非常眼熟。

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

接下來,我們就嘗試著在Fragment中使用一下Data Binding吧。

首先還是那個(gè)學(xué)生類,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)來了,我們這里data binding的操作都放在了fragment里,那么我們先來看看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)擊后會調(diào)用Fragment的click方法。我們來迫不及待的看一下Fragment怎么寫:

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對象,正好,我們這里可以使用onCreateView的第一個(gè)參數(shù),第二個(gè)參數(shù)是我們的布局文件,第三個(gè)參數(shù)是一個(gè)ViewGroup,第四個(gè)參數(shù)是一個(gè)boolean類型的,和在LayoutInflater.inflate一樣,后兩個(gè)參數(shù)決定了是否想container中添加我們加載進(jìn)來的布局。

下面的代碼和我們之前寫的并無差別,但是有一點(diǎn),onCreateView方法需要返回一個(gè)View對象,我們從哪獲取呢?ViewDataBinding有一個(gè)方法getRoot可以獲取我們加載的布局,是不是很簡單?

來看一下效果:


1.gif

10.Data Binding VS RecyclerView

有了上面的思路,大家是不是也會在ListView和RecyclerView中使用了?我們僅以一個(gè)RecyclerView來學(xué)習(xí)一下。

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

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

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

第二行代碼,executePendingBindings的作用是干嘛的?

官方的回答是:
當(dāng)數(shù)據(jù)改變時(shí),binding會在下一幀去改變數(shù)據(jù),如果我們需要立即改變,就去調(diào)用executePendingBindings方法。 所以這里的作用就是去讓數(shù)據(jù)的改變立即執(zhí)行。

ok,現(xiàn)在看起來,我們的代碼更加簡潔了,而且不需要保存控件的實(shí)例,是不是很爽? 來看看效果:


2.png

11.View with ID

在使用Data Binding的過程中,我們發(fā)現(xiàn)并沒有保存View的實(shí)例,但是現(xiàn)在我們有需求需要這個(gè)View的實(shí)例咋辦?難道走老路findViewById?當(dāng)然不是啦,當(dāng)我們需要某個(gè)view的實(shí)例時(shí),我們只要給該view一個(gè)id,然后Data Binding框架就會給我們自動生成該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中代碼沒有什么好說的,都是之前的代碼。需要注意的是, 我們給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);  //找到控件   
    }    
}

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

12.自定義setter(BindingAdapter)

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

這里我們增加了一個(gè)命名空間app,并且注意ImageView的app:image屬性,這里和我們自定義view時(shí)自定義的屬性一樣,但是這里并不需要我們?nèi)ブ貙慖mageView,這條屬性的值是我們上面定義的String類型的imageUrl,從名稱中看到這里我們可能會塞給他一個(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,那圖片怎么加載呢?這里就要使用到我們剛才說的BindingAdapter注解了。

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

我們定義了一個(gè)Utils類,這個(gè)類你可以隨便起名,該類中只有一個(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 注解了,來看看它的參數(shù)怎么定義的吧,難道是亂寫?當(dāng)然不是,這里要遵循一定的規(guī)則,

以bind:開頭,接著書寫你在控件中使用的自定義屬性名稱。

這里就是image了,不信來看。

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

13.Converters

Converter是什么呢?舉個(gè)例子吧:假如你的控件需要一個(gè)格式化好的時(shí)間,但是你只有一個(gè)Date類型額變量咋辦?肯定有人會說這個(gè)簡單,轉(zhuǎn)化完成后在設(shè)置,恩,這也是一種辦法,但是DataBinding還給我們提供了另外一種方式,雖然原理一樣,但是這種方式使用的場景更多,那就是——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類型的值,但是這里確給了一個(gè)Date類型的,這就需要我們?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類型的變量設(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è)類中,重要的是他的@BindingConversion注解,這個(gè)方法接受一個(gè)Date類型的變量,正好我們的android:text設(shè)置的就是一個(gè)Date類型的值,在方法內(nèi)部我們將這個(gè)Date類型的變量轉(zhuǎn)換成String類型的日期并且返回。這樣UI上就顯示出我們轉(zhuǎn)化好的字符串。


3.png

歡迎加入QQ群:104286694

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

推薦閱讀更多精彩內(nèi)容