Android官方数据绑定框架DataBinding

简介:

转自:http://blog.csdn.net/qibin0506/article/details/47393725

今天来了解一下android最新给我们带来的数据绑定框架——Data Binding Library。数据绑定框架给我们带来了更大的方便性,以前我们可能需要在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:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="stu"
            type="org.loader.androiddatabinding.Student" />
    </data>

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

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

可以看到我们的xml布局和以前还有有一定的差别的,但是差别也不是很大。 
最后来看看 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山东莱芜两句话。

)

在看完小实例后,大家是不是感觉棒棒哒? 没有了之前的find控件,没有了setText,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中定义变量,但是不知道你发现没?我们没有定义像ListMap等这样的集合变量。那到底能不能定义呢?答案肯定是可以的,而且定义的方式和我们上面的基本一致,区别就在于我们还需要为它定义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>

那么:DataBindingUtils.setContentView返回的binding类就是:你的应用包名.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");
    }
}

这段代码,首先显示的是loader,当我们点击TextView时,界面换成qibin。

ObservableFields家族 
上面使用BaseObservable已经非常容易了,但是google工程师还不满足,继续给我们封装了一系列的ObservableFields,这里有ObservableFieldObservableBoolean,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可以获取我们加载的布局,是不是很简单? 
来看一下效果:

十一、 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,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果:

十二、 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.setImageUrl("http://images.csdn.net/20150810/Blog-Image%E5%89%AF%E6%9C%AC.jpg");
    }
}

果然在这里我们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}"/>

看看运行结果:

十四、 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);
    }
}

和上面一样,我们不需要关心这个convertDate在哪个类中,重要的是他的@BindingConversion注解,这个方法接受一个Date类型的变量,正好我们的android:text设置的就是一个Date类型的值,在方法内部我们将这个Date类型的变量转换成String类型的日期并且返回。这样UI上就显示出我们转化好的字符串。 
看看效果:

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

参考链接:https://developer.android.com/tools/data-binding/guide.html

博客源码下载:代码下载,戳这里


相关文章
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
38 0
|
4月前
|
XML 物联网 API
Android Ble蓝牙App(五)数据操作
Android Ble蓝牙App(五)数据操作
|
4月前
|
数据库 Android开发 开发者
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
Android Studio入门之内容共享ContentProvider讲解以及实现共享数据实战(附源码 超详细必看)
39 0
|
7天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
9 0
|
7天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
8 0
|
21天前
|
XML Java Android开发
Android每点击一次按钮就添加一条数据
Android每点击一次按钮就添加一条数据
22 1
|
1月前
|
存储 Android开发 C++
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
【Android 从入门到出门】第五章:使用DataStore存储数据和测试
31 3
|
2月前
|
JavaScript Java 数据安全/隐私保护
安卓逆向 -- POST数据解密
安卓逆向 -- POST数据解密
25 2
|
3月前
|
编解码 测试技术 开发工具
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
如何实现Android视音频数据对接到GB28181平台(SmartGBD)
|
3月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
101 0