Data Binding Library(数据绑定库)

Posted AAA啊哈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Data Binding Library(数据绑定库)相关的知识,希望对你有一定的参考价值。

引子

xx
上图中有一些 TextView 和 Button 等,正常情况下,互联网APP都会从服务器抓取数值,然后在 Activity中 findViewById 再进行setText等等。这篇文章就是用来解放你的双手劳动力 的,使用数据绑定库可以不用去findView不用在写繁琐的 setText,只要从服务器获取json 转换成 javaBean格式然后 set,duang,,,,, 所有的值就自己展现在该有的地方了。

Demo: https://github.com/Afra55/DataBindingApplication

我自己认为,先看Demo,然后带着疑问 去阅读,会有一种解惑的情怀。>~<


原文:https://developer.android.com/intl/zh-cn/tools/data-binding/guide.html#build_environment

本文介绍了如何使用数据绑定库

数据绑定库提供了灵活性和广泛的兼容性-这是一个支持库,所以你可以在所有的android平台上使用它。这个库需要Android Plugin for Gradle 1.5.0-alpha1或更高版本。

构建环境

在 Android SDK中 下载支持库。
build.gradle 文件中添加 dataBinding 元素。

android {
    ....
    dataBinding {
        enabled = true
    }
}

此外,确保使用的是 Android Studio,

Data Binding Layout Files

写你的第一个数据绑定表达式

数据绑定布局和普通的布局稍有不同,有一个标签为 layout 的根布局,其次是元素 data 和一个视图根元素。例子:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

variable 的写法如下, type 的值指定完整路径的 java 类:

<variable name="user" type="com.example.User"/>

在布局属性参数中的表达式都要使用 “@{}”语法,在这里,TextView 的文本设置为 user 的 firstName 属性的值:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>

数据对象

现在假设你有了一个 User 对象:

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

这种类型的对象有一个永远不会改变的数据。这是应​​用中是常见的,以具有被写入一次数据并随后从不改变。另外,也可以使用一个JavaBeans对象:

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

从数据绑定的角度来看,这两个类是等价的。TextViewandroid:text 属性中使用的表达式 @{user.firstName} 会访问前面那个 User 类的 fristName 字段,或者后面那个 User 类的 getFirstName() 方法。或者,如果firstName()方法存在,它也将被解析为的 firstName()

绑定数据

默认情况下,绑定类将基于布局文件的名称来产生,上面的布局文件的名字是 main_activity.xml 通常情况下生成的类是 MainActivityBinding, 这个类包含所有从布局属性到布局视图的绑定 (e.g. the user variable),并且知道如何分配绑定表达式的值。下面是一个最简单的绑定的例子:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

这样,就完成了绑定,运行程序,就可以在 ui 上看到 user 对象的数据了。另外,还可以这样获取视图:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

如果你正在使用数据绑定在一个ListView或RecyclerView的适配器中,最好这么用:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

绑定事件

事件的绑定可能直接用来处理方法,例如 android:onClick 可以直接给 Activity 分配一个方法。事件的属性名是由监听的方法分配的,例如View.OnLongClickListener有一个方法onLongClick() ,因此这个事件的属性是 android:onLongClick
分配一个事件处理机制,就是在表达式中调用这个方法名。例如,如果你的数据对象有两个方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
    public void onClickEnemy(View view) { ... }
}

下面的例子就是通过表达式给 view 分配了个点击监听的事件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
   </LinearLayout>
</layout>

一些特殊的点击事件需要其他的属性来避免与 android:onClick 的冲突,已经创建了下面的属性,以避免这样的冲突:

ClassListener SetterAttribute
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

布局细节

Imports

import 元素允许你的布局文件引用类,就像在java文件中引用一样:

<data>
    <import type="android.view.View"/>
</data>

现在,View 就可以在表达式中使用了:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

当你想引入自己写的一个 View 类的时候,你可以使用“alias”起个别名:

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

现在,Vista 用来引用 com.example.real.estate.View,View 用来引用 android.view.View。引用类型也可能在变量的type里使用:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List"/>
</data>
<TextView
   android:text="@{((User)(userList.get(0))).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

import类型可能在要引用静态方法的时候使用:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data><TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

就好像 Java, java.lang.* ,自动导入。

Variablesb(变量)

可以在 data 元素中使用任意的 variable 元素。一个 variable 元素描述了一个可能在布局文件中的绑定表达式中使用的属性。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

变量的 type 是在编译时检查,因此如果一个变量实现了 Observable 或者是一个 Observable 集合,应在 type 中进行反映。如果变量是一个没有实现 Observable* 接口的基类或者接口,变量将不会被发现。
当有不同配置(如横向或纵向)的不同布局文件,变量将被合并。有这些布局文件之间一定不能有相互冲突的变量定义。
生成的绑定类会为每一个描述的变量生成一个 getter 和 setter 方法。在 setter 被调用之前,变量将采取默认的 java 值——null(引用类型),0(int),false(布尔类型)等。
一个叫 context 的特殊变量会在绑定表达式中需要的时候生成,这是 context 变量的 value 值是 Context (从根视图的 getContext 方法中得到的)。这个 context 变量会被同名的 显示声明 变量覆盖掉。

自定义绑定类的名字

默认情况下,绑定类的名字是基于布局文件的名字生成的。将第一个字母大写,去掉下划线将接下来的字母接上并大写第一个字母,最后跟上“Binding”。例如,布局文件 contact_item.xml 会生成 ContactItemBinding,如果这个模块的包名是 com.example.my.app,那么生成的绑定类会放置在 com.example.my.app.databinding
绑定类可以通过改变 data 元素下的 class 属性来重命名或者放置到其他包里。例如:

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

这样绑定类就被重命名为 ContactItem 。如果绑定类要在模块包下的其他package下生成,则要加前缀 “.”。例如:

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

这种情况下,绑定类会放到 模块包.ContactItem下即 com.example.my.app.ContactItem 包下。如果要完全自定义包名,则提供完整路径即可:

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

Includes

变量可能会被传入到引用的布局中,这么做:

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

included 的布局必须要有 user 变量,例如:

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

    <data>
        <variable
            name="user"
            type="com.example.User" />
    </data>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.sex}"/>

</layout>

绑定数据不支持 merge 元素的布局引用,以下布局是不支持的:

<?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>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

表达式语言

公共特性

这些表达式看起来很像java表达式,下面这些都是一样的:

  • 数学计算 + - / *%
  • 连接字符串 +
  • 逻辑运算 && ||
  • 二进制运算 & | ^
  • 单目运算 + - ! ~
  • 移位运算 >> >>> <<
  • 比较运算 == > < >= <=
  • instanceof
  • 分组 ()
  • 常量 character, String, numeric, null
  • 造型运算符(Cast)
  • 方法调用
  • 字段访问
  • 访问数组 []
  • 三元运算 ?:

例如:

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

以下操作不可用

  • this
  • super
  • new
  • Explicit generic invocation

空合并运算

空合并运算符(??),如果左面的不为 null 则选择左边的,否则选择右边。

android:text="@{user.displayName ?? user.lastName}"

它等价于下面的公式:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

引用属性

android:text="@{user.lastName}"

#### 避免 NullPointerException
生成的数据绑定代码会自动检查空值和避免空指针异常。例如表达式@{user.name},如果 user 是 null, user.name 就会被指定默认值 (null)。如果引用 user.age,由于 age 是 int 类型,所以指定默认值为 0。

集合

常见的集合:arrays, lists, sparse lists, 和 maps,可能使用 [] 操作符来进行访问。

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="MapM<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

字符串常量

当使用单引号包含属性值时,在表达式中使用双引号是个简易的方法来表示常量:

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

如果使用双引号包含属性值时,在表达式中使用单引号或者 &quot; 表示常量:

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

Resources

可以使用普通的访问资源的表达式,如下事例:

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

当格式化 string 或者 plurals 需要提供参数:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

当 plurals 有多个参数时,每个参数都要传入:


  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

下面一些资源的显示调用跟普通的调用略有不同:
reference

Data Objects

当绑定了 java 对象后,再对他进行修改并不会让 UI 更新,这里有三个不同的数据通知机制:

当它们中的一个观察到绑定在 UI 上的数据对象被修改时,UI 就会自动更新。

Observable Objects

一个类实现 Observable 接口允许绑定对象绑定一个 listener 来监听对象所有的 属性变化。
为了让开发变得简单,有个名为 BaseObservable 的基类用来实现 listener 的注册机制,实现了这个基类的数据类会一直负责通知属性的变化,通知的方式是通过指定 getter 方法 Bindable 注解和在 setter 方法的通知来实现的。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

在编译的时候注解 Bindable 会在 BR 类中生成一个属性的入口,以便在 setter 方法中通知。BR 类会在模块包下生成。

ObservableFields

Observale 类的创建会有一小部分的准备工作,因此开发者想要节约时间或者只监听少部分的属性,这时可以使用 ObservableField 和它的兄弟们 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable。ObservableFields 是一个独立的自给自足的 ObservableFields 对象,拥有一个独立的字段。在访问原始数据时避免了 boxing 和 unboxing 操作。如下事例,在数据类中创建 public final 字段:

public class Son {
    public final ObservableField<String> firstName =
            new ObservableField<>();
    public final ObservableField<String> lastName =
            new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}

下面是访问和设置值的例子:

son.firstName.set("Child");
int age = son.age.get();

Observable 集合

一些应用使用更多的动态结构来保存数据。Observable 集合允许使用 key-value 的形式来保存数据。 ObservableArrayMap 在当 key 是个引用类型(比如 string)的时候非常有用。

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

在 布局中, 使用 string key 来访问数据:

<data>
    <import type="android.databinding.ObservableArrayMap"/>
    <variable name="user" type="ObservableArrayMap"/>
</data><TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

ObservableArrayList 在key 是 数字的时候使用,和 ArrayList 类似:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

在布局中,通过索引来访问数据:

<data>
    <import type="android.databinding.ObservableArrayList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableArrayList"/>
</data><TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

生成绑定

生成的绑定类用来链接变量和布局中的视图。上文已经说过了,绑定类的名字和存储的位置都是可以自定义的。绑定类都会继承一个父类 ViewDataBinding

创建

在布局中的视图和表达式绑定之后会很快生成绑定类。这里有几种绑定布局的方法,其中最常见的是使用绑定类中的静态方法,只需调用 inflate 方法这一步即可填充布局和绑定布局:

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

如果只绑定布局,可以使用下面的方法:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时候绑定不能被提前知道,在这种情况下可以使用 DataBindingUtil 类(该类的一些方法与文中有不同,具体看源码):

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

Views With IDs

布局中每一个视图的 ID 都会生成一个 public final 字段。绑定机制会在视图层次提取视图的 ID,这种方式来获取视图要比调用 findViewById 快。例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

生成的绑定类会有下面的字段:

public final TextView firstName;
public final TextView lastName;

然后可以直接使用:

mBinding.lastName.setText("Afra55");

变量

每一个变量都会有一个方法去访问它。

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

会在绑定类中生成相应的 setter 和 getter 方法:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

ViewStubs

ViewStubs 默认是不显示的,加载时才会被其他布局替换而显示出来。
ViewStub 本质上是不会出现在视图层次的, 由于 Views 是final类型,所以 ViewStub绑定后就会转换为 ViewStubProxy对象,让开发着可以在 ViewStub 存在并被填充时访问 ViewStub。
当填充另一个布局时,新布局的绑定也要被建立。因此, ViewStubProxy 一定要监听 ViewStub 的 ViewStub.OnInflateListener 接口 和及时建立绑定。因此当绑定的建立完成时 ViewStubProxy 允许开发者设置 OnInflateListener 来回调。
举例:
布局中添加 StubView:

            <ViewStub
                android:id="@+id/viewstub"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:layout="@layout/test_viewstub" />

test_viewstub.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.databinding.databindingapplication.User"/>
    </data>
<TextView
    android:orientation="vertical"
    android:text='@{user.lastName + " StubViewProxy"}'
    android:gravity="center"
    android:textColor="@android:color/black"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
</layout>

在代码中引用:

// 设置布局加载监听用来绑定数据,绑定后 viewStub 转换成 ViewStubProxy 
 mBinding.viewstub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                TestViewstubBinding viewDataBinding = DataBindingUtil.bind(inflated);
                User user = new User("xx", "gg", true);
                viewDataBinding.setUser(user);
            }
        });
// 显示布局,Andoird studio会报红,可能还么支持,直接运行即可。 
if (!mBinding.viewstub.isInflated()) {
                mBinding.viewstub.getViewStub().inflate();
            }

Advanced Binding

动态变量

有时,一些特殊的绑定类不会被知道。例如,一个 RecyclerView.Adapter 里的操作不允许任何布局知道绑定类。但是依旧会在 onBindViewHolder(VH, int). 里分配绑定值。
在这个例子里,所有RecyclerView 布局的绑定都会有一个 “item” 变量。BindingHolder 自己实现一个 getBinding 方法用来获取 ViewDataBinding。

@Override
public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

这个例子需要去实现 RecyclerView.Adapter 。我自己实现了个例子:
主布局:

<android.support.v7.widget.RecyclerView
                android:id="@+id/recylerview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

Item 布局:

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

    <data>

        <variable
            name="user"
            type="com.example.databinding.databindingapplication.User" />
    </data>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

  

以上是关于Data Binding Library(数据绑定库)的主要内容,如果未能解决你的问题,请参考以下文章

WPF中的数据绑定Data Binding使用小结

告别findViewById(),ButterKnife,使用Google Data Binding Library

记一次 Data Binding 在 library module 中遇到的大坑

XAML数据绑定(Data Binding)

data Binding

Data Binding MVVM 数据绑定 总结