​Activity​​里写很多的​​findViewById​​,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃那么多的​​findViewById​​。说到这里,有人可能会有个疑问:我使用一些注解框架也可以不用​​findViewById​​啊,是的,但是注解注定要拖慢我们代码的速度,Data Binding则不会,官网文档说还会提高解析XML的速度,最主要的Data Binding并不是单单减少了我们的​​findViewById​​,更多好处请往下看文章。 一、环境 

在开始使用新东西之前,我们需要稍微的配置一下环境,这里要求你的Android Studio版本是1.3+,使用eclipse的同学暂时还没有办法使用该框架,请换用Android Studio。还有,在开始之前,请更新你的​​Support repository​​到最新的版本。 

万事俱备,那我们就开始搭配环境!​​project​​,在​​dependencies​​中添加以下依赖

classpath "com.android.databinding:dataBinder:1.0-rc1"

​module​​,并且在​​module​​的build.gradle文件中添加

apply plugin: 'com.android.application'
apply plugin: 'com.android.databinding'

ok,到现在为止,我们的环境就准备完毕了,下面我们就开始Data Binding的学习啦。

二、Data Binding尝试 

在代码开始,我们并不直接进入新东西的讲解,而且以一段代码展现Data Binding的魅力。 

首先我们需要一个​​java bean​​,很简单,一个学生类。

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;
}
}


再来看看我们布局文件怎么写:


<layout xmlns:ndroid">
<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布局和以前还有有一定的差别的,但是差别也不是很大。 


最后来看看

​Activity​

怎么写。


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​​的代码非常简单,就添加了两行代码,而且,值得注意的是:我们并没有​​findViewById​​然后再去​​setText​​。 

这段小代码运行的结果大家可能已经猜到了,就是在界面上显示​​loader​​和​​山东莱芜​​两句话。

Android官方数据绑定框架DataBinding_xml

)​​Activity​​代码更加简洁明了! 

下面开始,我们进入Data Binding的学习!

三、 初始Data Binding 
上面的代码算是带领我们进入了Data Binding的世界,那我们先从布局文件开始入手Data Binding吧。再来看看上面的布局文件。

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


我们的根节点变成了

​layout​

,在

​layout​

的子节点中分成两部分,第一部分是

​data​

节点,第二部分才是我们之前的根节点,在

​data​

节点下我们又定义了一个

​variable​

, 


从名称上看,这应该是一个变量,变量的名称是

​stu​

,类型是

​org.loader.androiddatabinding.Student​

,这类似我们在java文件中这么定义:


org.loader.androiddatabinding.Student stu;

ok,这样很好理解了吧,不过这里要写

​Student​

完整的包名,一个还好,如果这里我们需要多个

​Student​

呢?要累死? NO,NO,NO,我们还可以向写java文件那样导入包。

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


这样写,就类似于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>


恩,注意看两个

​TextView​


​android:text​

,它的值是一个以

​@​

开始,以{}包裹的形式出现,而内容呢?是

​stu.name​

。stu就是我们上面定义的

​variable​



name还记得吗?是我们

​Student​

类中的一个变量。其实这里就会去调用

​stu.getName()​

方法。 


好了,很快,我们就入门了Data Binding,下面让我们来多定义几个变量试试看。

<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>


来看看定义的变量,多了好几个,有一个

​String​

类型的变量我们并没有导包,这里说明一下,和在java里一样,

​java.lang​

包里的类,我们是可以不用导包的,再往下,一个

​boolean​


​int​

类型的变量,都是java基本类型的,所以说嘛,在这里定义变量,你就想成是在java里定义就ok。 


再来看看这几个

​TextView​

,第二个,我们直接使用

​@{str}​

来为

​android:text​

设置成上面定义个

​str​

的值,继续往下要注意了,我们使用了

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

​int​​类型的变量,大家都知道我们在给​​android:text​​设置​​int​​类型的值时一定要转化为​​String​​类型,要不它就认为是资源文件了,这里我们还学到了一点,在xml中,我们不仅可以使用变量,而且还可以调用方法! 四、 变量定义的高级部分 

在上面,我们学会了如何去在xml中定义变量,但是不知道你发现没?我们没有定义像​​List​​、​​Map​​等这样的集合变量。那到底能不能定义呢?答案肯定是可以的,而且定义的方式和我们上面的基本一致,区别就在于我们还需要为它定义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>


​ArrayList&lt;String>​​。 

下面我们还为下面使用这些集合准备了几个key,也都是变量。 

继续看看怎么使用,和我们在java中使用不同,这里都是以:集合变量名[key]的形式使用,如果你的key是一个字面字符串可以使用反引号,也可以使用转义后的双引号。恩,这里也没有什么可以说的了,大家多看几遍就掌握了,都是概念性的东西,记住就ok。 五、在java代码中使用 

前面定义了这么多变量,但是我们还没有给他们赋值!在哪赋值呢?肯定是在java代码中使用了,大部分情况我们还是在​​Activity​​中去使用它,以前我们都是在​​onCreate​​方法中通过​​setContentView​​去设置布局,但现在不一样了,现在我们是用过​​DataBindingUtil​​类的一个静态方法​​setContentView​​设置布局,同时该方法会返回一个对象,什么对象?这个对象有点特殊,它是一个自动生成的类的对象,看下面:

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


看到

​ActivityMainBinding​

了吗?就是它!那自动生成有什么规则了没?当然有了,记好了:

将我们布局文件的首字母大写,并且去掉下划线,将下划线后面的字母大写,加上Binding组成。

看看上面的类,是不是符合这个规则。继续看看这个对象哪来的,是通过

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


返回的,DataBindingUtil.setContentView的两个参数分别是当前

​Activity​

和布局文件。那接下来,就是我们关心的给变量赋值了。

@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,这个XXX是什么呢?就是我们在xml中定义的那些变量首字母大写了!也没好好说的吧,多看几遍。

六、 表达式 
短暂的幸福时光,我们还是要告别java代码了,继续回到xml中,这一块,我们来学习一下表达式,什么?这玩意在xml中还支持表达式!

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


还记得上面我们定义了一个boolean的变量没有用到,这里我们就用到了,看好

​android:text​

,这里是一个三元表达式,如果error是true,则text就是error,否则是ok。这里还支持null合并操作,什么是null合并,相信看一眼你就知道了

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


简单解释一下,如果str是null,text的值就是str本身,否则就是”not null”。 
它还支持一下表达式:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:

但是它不支持一下表达式:

  • this
  • super
  • new
  • Explicit generic invocation

七、 其他遗漏点 
说到这里,xml中的事情基本算完了,但是还有几个小地方没有说,顺便说一下。 
1. 设置别名 
假如我们import了两个相同名称的类咋办?别怕,别名来拯救你!例如:

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


  1. 自定义Binding名称 
    还记得系统为我们生成好的那个binding类名吗?如果只能使用那样的是不是有点太暴力了?好在google对我们还算友好了,允许我们自定义binding名称,定制名称也很简单,就是给data一个class字段就ok。 
    例如:

<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box; font-family: 'Source Code Pro', monospace; font-size: 14px; line-height: 20px; white-space: pre; background-color: rgba(128, 128, 128, 0.0470588);"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">data</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">class</span>=<span class="hljs-value" style="box-sizing: border-box; color: rgb(0, 136, 0);">".Custom"</span>></span><span style="color: rgb(51, 51, 51); font-family: 'Source Code Pro', monospace; font-size: 14px; line-height: 20px; white-space: pre; background-color: rgba(128, 128, 128, 0.0470588);">...</span><span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box; font-family: 'Source Code Pro', monospace; font-size: 14px; line-height: 20px; white-space: pre; background-color: rgba(128, 128, 128, 0.0470588);"></<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">data</span>></span>


​你的应用包名.Custom​​。 八、事件绑定 

大家都知道,在xml中我们可以给​​button​​设置一个​​onClick​​来达到事件的绑定,现在DataBinding也提供了事件绑定,而且不仅仅是​​button​​。 

来看一下:

<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>


定义了一个

​EventHandlers​

类型的

​handlers​

变量,并在onClick的时候执行

​EventHandlers​


​handleClick​

方法。 


继续看看EventHandlers是怎么写的。


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


​Toast​​了一下,这里要注意的是,​​handlerClick​​方法需要一个​​View​​的参数。 九、 数据对象 

我们学会了通过binding为我们的变量设置数据,但是不知道你有没有发现一个问题,当我们数据改变的时候会怎样?数据是跟随着改变呢?还是原来的数据呢?这里告诉你答案:很不幸,显示的还是原来的数据?那有没有办法让数据源发生变化后显示的数据也随之发生变化?先来想想​​ListView​​是怎么做的, ​​ListView​​的数据是通过​​Adapter​​提供的,当数据发生改变时,我们通过​​notifyDatasetChanged​​通过UI去改变数据,这里面的原理其实就是内容观察者,庆幸的是DataBinding也支持内容观察者,而且使用起来也相当方便! BaseObservable 

我们可以通过Observable的方式去通知UI数据已经改变了,当然了,官方为我们提供了更加简便的方式​​BaseObservable​​,我们的实体类只需要继承该类,稍做几个操作,就能轻松实现数据变化的通知。如何使用呢? 首先我们的实体类要继承​​BaseObservale​​类,第二步在​​Getter​​上使用注解​​@Bindable​​,第三步,在​​Setter​​里调用方法​​notifyPropertyChanged​​,第四步,完成。就是这么简单,下面我们来实际操作一下。 

首先定义一个实体类,并继承​​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,我们调用了

​notifyPropertyChanged​

方法,这个方法还需要一个参数,这里参数类似于

​R.java​

,保存了我们所有变量的引用地址,这里我们使用了name。 


再来看看布局文件。


<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>


不多说了,我们给

​TextView​

设置了文本,还有点击事件。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");
}
}


​TextView​​时,界面换成qibin。 ObservableFields家族 

上面使用​​BaseObservable​​已经非常容易了,但是google工程师还不满足,继续给我们封装了一系列的​​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();
}


很简单,只有三个ObservableField变量,并且没有getter和setter,因为我们不需要getter和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);
...


也不多说了。

Observable Collections 

既然普通的变量我们有了ObservableFields的分装,那集合呢?当然也有啦,来看着两个:​​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文件,怎么设置数据,


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


太简单了,简直和

​List​


​Map​

使用方法一模一样!!! 


​​demo源码下载,戳这里​​

十、inflate 

不知道大家注意没有,上面的代码我们都是在activity中通过​​DataBindingUtil.setContentView​​来加载的布局的,现在有个问题了,如果我们是在​​Fragment​​中使用呢?​​Fragment​​没有​​setContentView​​怎么办?不要着急,Data Binding也提供了​​inflate​​的支持! 

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

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


接下来,我们就尝试着在

​Fragment​

中使用一下Data Binding吧。 


首先还是那个学生类,

​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的代码,


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();
}
}

重点来了,我们这里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>

两个TextView分别绑定了Student的name和age字段,而且给name添加了一个点击事件,点击后会调用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个参数,第一个参数是一个LayoutInflater对象,正好,我们这里可以使用onCreateView的第一个参数,第二个参数是我们的布局文件,第三个参数是一个ViewGroup,第四个参数是一个boolean类型的,和在​​LayoutInflater.inflate​​一样,后两个参数决定了是否想​​container​​中添加我们加载进来的布局。 

下面的代码和我们之前写的并无差别,但是有一点,​​onCreateView​​方法需要返回一个View对象,我们从哪获取呢?​​ViewDataBinding​​有一个方法​​getRoot​​可以获取我们加载的布局,是不是很简单? 

来看一下效果:

Android官方数据绑定框架DataBinding_android_02

十一、 Data Binding VS RecyclerView 
有了上面的思路,大家是不是也会在ListView和RecyclerView中使用了?我们仅以一个RecyclerView来学习一下。 
首先来看看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>


可以看到,还是用了那个Student实体,这样得代码,相信你也已经看烦了吧。 


那我们来看看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​

设置了一个Adapter,相信最主要的代码就在这个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;
}
}


果然,这个adapter的写法和我们之前的写法不太一样,首先看看ViewHolder,在这个holder里,我们保存了一个

​ViewDataBinding​

对象,并给它提供了

​Getter​


​Setter​

方法, 这个

​ViewDataBinding​

是干嘛的?我们稍后去讲。继续看看

​onCreateViewHolder​

,在这里面,我们首先调用

​DataBindingUtil.inflate​

方法返回了一个

​ViewDataBinding​

的对象,这个

​ViewDataBinding​

是个啥?我们以前没见过啊,这里告诉大家我们之前返回的那些都是

​ViewDataBinding​

的子类!继续看代码,我们new了一个holder,参数是肯定是我们的item布局了,继续看,接着我们又把binding设置给了holder,最后返回holder。这时候,我们的holder里就保存了刚刚返回的

​ViewDataBinding​

对象,干嘛用呢?继续看

​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​

这样方法去设置变量,那这个

​setVariable​

呢? 为什么没有

​setStu​

,这里要记住,

​ViewDataBinding​

是我们之前用的那些binding的父类,只有自动生成的那些子类才会有

​setXXX​

方法,那现在我们需要在

​ViewDataBinding​

中设置变量咋办?这个类为我们提供了

​setVariable​

去设置变量,第一个参数是我们的变量名的引用,第二个是我们要设置的值。第二行代码,

​executePendingBindings​

的作用是干嘛的?官方的回答是:


当数据改变时,binding会在下一帧去改变数据,如果我们需要立即改变,就去调用

​executePendingBindings​

方法。

所以这里的作用就是去让数据的改变立即执行。 
ok,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果:


Android官方数据绑定框架DataBinding_java_03

十二、 View with ID 

在使用Data Binding的过程中,我们发现并没有保存View的实例,但是现在我们有需求需要这个View的实例咋办?难道走老路​​findViewById​​?当然不是啦,当我们需要某个view的实例时,我们只要给该view一个id,然后Data Binding框架就会给我们自动生成该view的实例,放哪了?当然是​​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​

了一个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​​类的实例直接去获取的。

只要我们给了view一个id,那么框架就会在ViewDataBinding中自动帮我们保存这个view的实例,变量名就是我们设置的id。

十三、 自定义setter 

想想这样的一种情景,一个​​ImageView​​需要通过网络去加载图片,那我们怎么办?看似好像使用DataBinding不行,恩,我们上面所学到东西确实不能够解决这个问题,但是DataBinding框架给我们提供了很好的扩展,允许我们自定义setter,那该怎么做呢?这里就要引出另一个知识点——​​BindingAdapter​​,这是一个注解,参数是一个数组,数组中存放的是我们自定义的’属性’。接下来就以一个例子学习一下​​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>


这里我们增加了一个命名空间

​app​

,并且注意ImageView的

​app:image​

属性,这里和我们自定义view时自定义的属性一样,但是这里并不需要我们去重写ImageView,这条属性的值是我们上面定义的String类型的imageUrl,从名称中看到这里我们可能会塞给他一个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.setImageUr
}
}


果然在这里我们set了一个url,那图片怎么加载呢?这里就要使用到我们刚才说的BindingAdapter注解了。


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


​Utils​​类,这个类你可以随便起名,该类中只有一个静态的方法imageLoader,该方法有两个参数,一个是需要设置数据的view, 

一个是我们需要的url。值得注意的是那个​​BindingAdapter​​注解,看看他的参数,是一个数组,内容只有一个​​bind:image​​,仅仅几行代码,我们不需要 

手工调用Utils.imageLoader,也不需要知道imageLoader方法定义到哪了,一个网络图片加载就搞定了,是不是很神奇,这里面起关键作用的就是​​BindingAdapter​​ 

注解了,来看看它的参数怎么定义的吧,难道是乱写?当然不是,这里要遵循一定的规则,

以bind:开头,接着书写你在控件中使用的自定义属性名称。

​image​​了,不信来看。

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


看看运行结果:


Android官方数据绑定框架DataBinding_java_04

十四、 Converters 

Converter是什么呢?举个例子吧:假如你的控件需要一个格式化好的时间,但是你只有一个​​Date​​类型额变量咋办?肯定有人会说这个简单,转化完成后在设置,恩,这也是一种办法,但是DataBinding还给我们提供了另外一种方式,虽然原理一样,但是这种方式使用的场景更多,那就是——Converter。和上面的​​BindingAdapter​​使用方法一样,这也是一个注解。下面还是以一段代码的形式进行学习。

<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属性,我们需要一个String类型的值,但是这里确给了一个Date类型的,这就需要我们去定义Converter去转换它, 


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());
}
}


去给这个Date类型的变量设置值。怎么去定义Converter呢? 看代码:


public class Utils {

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


​@BindingConversion​​注解,这个方法接受一个Date类型的变量,正好我们的android:text设置的就是一个Date类型的值,在方法内部我们将这个Date类型的变量转换成String类型的日期并且返回。这样UI上就显示出我们转化好的字符串。 

看看效果:

Android官方数据绑定框架DataBinding_xml_05

好了,到这里DataBinding的知识我们就算学习完了,在学完之后发现这东西也没什么难度,学会使用就ok了,而且android官网也有非常详细的文档, 
这两篇博客只是系统的去讲解了DataBinding的使用,大家在以后使用的过程中发现忘记怎么用了,可以再来翻看博客或者直接去官方查看。 
ok, 那就到这里吧,下次见。