Android官方数据绑定框架DataBinding

Android新推出了一个官方的数据绑定框架Data Binding Library,既然是官方推出的新玩意,我们就有必要了解一下Android新带来的数据绑定框架,等到该框架推出正式版的时候,我们就可以快速地运用到项目中去。数据绑定框架给我们带来了很大的方便性,以前我们可能需要在Activity里写很多的findViewById,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃那些findViewById。说到这里,有人可能会问:我使用的一些注解框架也可以不用findViewById啊,是的,但是注解的缺点是拖累代码的效率,Data Binding则不会,Android官方文档说还会提高解析XML的速度,最主要的是Data Binding并不是单单减少我们的findViewById,更多的好处我们接下来一起探寻。

1.环境

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

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

然后修改对应模块(Module)的build.gradle,添加如下脚本代码:

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

最后,点击Sync同步一下Gradle即可完成环境配置

2.Data Binding示例

首先,我们需要新建一个Java Bean,一个简单的学生类。

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>

最后,实现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详解

上面的示例仅仅是带领我们进入了Data Binding的世界,接下来我们解释一下Data Binding的开发步骤。先看看上面的布局文件。

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

根节点使用的是layout,在layout中分成两部分,第一部分是data节点,第二部分才是我们布局的根节点,在data节点下我们定义了一个variable,它是一个变量,变量名称是stu,类型是com.example.iaiai.databinding.Student,这类似我们在java文件中的定义:

com.example.iaiai.databinding.Student stu;

不过这里要写Student完整的包名,如果这里我们需要多个Student呢?我们可以像写java文件那样导入类包

<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" />  
    </data>  
  
    .....  
  
</layout>

这样就类似于java中的

import com.example.iaiai.databinding.Student;  
  
Student stu1,stu2,...

既然变量定义好了,那该怎么使用呢?我们仍然看上面的xml文件

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

由上面可以看出,两个TextView的android:text,它的值是以@开始,以{}包裹的形式出现,而值呢?是stu.name,stu就是上面定义的variable,name就是Student类中的成员变量,其实这里就会去调用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去设置布局。但现在不一样了,现在我们是通过DataBindingUtil类的一个静态方法setContentView设置布局,同时该方法会返回一个对象,这个对象时一个自动生成的类的对象,如DataBindingBinding?那么它的命名规则是什么呢?将我们布局文件的首字母大写,并且去掉下划线,将下划线后面的字母大写,加上后缀Binding组成。最后,我们通过这个对象来给变量赋值。

通过以上分析,我们了解Data Binding的具体开发步骤,下面让我们定义不同的几个变量看看

<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类型的变量没有导入包,这是因为Data Binding和Java一样,java.lang包里的类,我们是可以不用导入包的,再往下一个boolean和int类型的变量,都是java基本类型,也不用导入包。

再来看看几个TextView,第二个TextView,我们直接使用@{str}来为android:text设置文本内容;接下来注意第三个TextView,我们使用android:text="@{String.valueOf(num)}"来设置一个int类型的变量,因为在给android:text设置int类型的值一定要转化为String类型,不然系统会认为是资源文件id。此外,我们还学习到了一点,在Xml中,我们不仅可以使用变量,而且还可以调用方法

  1. 变量定义的高级部分

在上面,我们学会了如何在xml中定义变量,但是我们并没有定义像List、Map等这样的集合变量。那么到底能不能定义呢?答案是肯定的,而且定义的方式和我们上面的基本一致,区别就在于我们还需要为它定义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>  
        <!--导入类包-->  
        <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.表达式

xml中还支持表达式

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

如上所示,android:text后是一个三元表达式,如果error是true,则text就是error,否则是OK。

除此外还支持null合并操作,??--左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象

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

还支持以下表达式:

  • 数学 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元运算 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 分组 ()
  • null
  • Cast
  • 方法调用
  • 数据访问 []
  • 三元运算 ?:

示例:

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

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

  • this
  • super
  • new
  • 显式泛型调用
  1. 其他知识点

6.1 设置别名

假如我们import了两个相同名称的类咋办?我们可以借助于别名来解决,别名借助alias字段来标识,例如:

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

6.2 自定义Binding类名称

默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding封装包下。例如,所述layout文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放置在com.example.my.app.databinding。

Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:

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

在模块封装包的databinding包中会生成名为ContactItem的Binding类。如果要想让该类生成在不同的包中,你需要添加前缀.,如下:

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

在这个情况下,ContactItem类直接在模块包中生成。或者你可以提供整个包名:

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

6.3 字符串

当使用单引号包含属性值时,在表达式中使用双引号很容易:

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

使用双引号来包含属性值也是可以的。字符串前后需要使用"":

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

6.4 Resources

使用正常的表达式来访问resources也是可行的:

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

6.5 include

通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的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两个layout文件中必需要有user variable

7.事件绑定

大家都知道,在xml中我们可以给button设置一个onClick来达到事件的绑定,现在DataBinding也提供了事件绑定,而且不仅仅是button。首先定义一个对象处理点击事件,如下:

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

最后,实现事件绑定

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

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

8.1 Observable

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

首先定义一个实体类,并继承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,我们调用了notifyPropertyChanged方法,这个方法还需要一个参数,这里参数类似于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实现

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); //设置初始显示数据  
        binding.setClick(this); //设置点击事件  
  
    }  
  
  
    public void click(View view) {  
        //点击时数据发生改变  
        mStu.setName("lee");  
        mStu.setAddr("Beijing");  
    }  
}

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

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

9.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可以获取我们加载的布局,是不是很简单?

来看一下效果:


1.gif

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


2.png

11.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。

12.自定义setter(BindingAdapter)

想想这样的一种情景,一个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}"/>

13.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上就显示出我们转化好的字符串。


3.png

欢迎加入QQ群:104286694

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容