Data Binding 从入门到精通

原文出自:http://blog.csdn.net/xx326664162/article/details/62048543 薛瑄的博客

虽然官方给出了教程,Data Binding LibraryAndroid Data Binding(数据绑定)用户指南),但是由于近来的更新,发现官方文档并没有更新。有时候看了官方文档,感觉还是不太清楚在讲什么,文章中有些地方我会从实战角度介绍一下,也拓展一些内容,比如双向绑定等。

1、介绍

这篇文章介绍了如何使用Data Binding库,并且用最少的代码来绑定工程中的java文件和layouts文件。

Data Binding库不仅灵活而且广泛兼容- 它是一个support库,因此你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。

需求:Android Plugin for Gradle 1.5.0-alpha1 或 更高版本。

2、构建环境

在2015年的谷歌IO大会上,Android UI Toolkit团队发布了DataBinding 框架,将数据绑定引入了Android开发,当时还只支持单向绑定,而且需要作为第三方依赖引入,时隔一年,双向绑定这个特性也得到了支持,同时纳入了Android Gradle Plugin(1.5.0+)中,只需要在gradle配置文件里添加短短的三行,就能用上数据绑定。

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

请确保您使用的是Android Studio的兼容版本。Android Studio的Data Binding插件需要Android Studio 1.3.0 或 更高版本。

在继续学习下面内容前,先说一些概念,有个大概印象。Data Binding的使用,需要xml和java的共同配合。在xml中需要data元素,在java中需要Binding类。

3、Data Binding 中的xml文件

3.1、xml文件中使用Data Binding

Data Binding layout文件有点不同的是:起始根标签是layout,接下来一个data元素以及一个view根元素。这个view元素就是你原来没有使用Data Binding的layout文件的根元素。举例说明如下:

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

在data内描述了一个名为user的变量属性,使其可以在这个layout中使用:

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

在layout的属性表达式写作@{},下面是一个TextView的text设置为user的firstName属性:

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

3.2、Data对象

假设你有一个user的plain-old Java Object(POJO):

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

这个类型的对象拥有从不改变的数据。在app中它是常见的,可以读取一次并且之后从不改变。

当然也可以使用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;
   }
}

从Data Binding的角度来看,这两个类是等价的。TextView中的android:text属性的表达式@{user.firstName}将访问POJO对象中的firstName或者JavaBeans对象中的getFirstName()

3.3、绑定数据

默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为单词首字母大写并添加“Binding”后缀。上述的layout文件是main_activity.xml,生成的类名是MainActivityBinding。

此类包含layout属性和layout的Views中所有的bindings(例如user变量),并且它还知道如何给Binding表达式赋值。

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

就是这样,运行app后,你将会看到Test User。

或者你可以通过如下获取binding:

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

如果你在ListView或者RecyclerView adapter使用Data Binding时,你可能会使用:

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

3.4、事件处理

数据绑定允许你编写表达式来处理view分派的事件。事件属性名字取决于监听器方法名字。例如View.OnLongClickListener有onLongClick()的方法,因此这个事件的属性是android:onLongClick。处理事件有两种方法:

4、深入了解Layout文件

4.1、Import

零个或多个import元素可以在data元素中使用:

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

现在,View可以使用Binding表达式:

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

当类名有冲突时,把一个类重命名为alias::

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

这样,在该layout文件中Vista对应com.example.real.estate.View,而View对应android.view.View。导入的类型可以以Variable和表达式的形式当做type的参数使用:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
 </data>

注意:Android Studio还没有处理imports,所以自动导入Variable在你的IDE不能使用。您的app仍会正常编译,你可以在您的Variable定义中使用完全符合规定的名称来解决该IDE问题。

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

还可以在表达式中使用static属性和方法:

<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.*是自动导入的。

4.2、Variables

在data中可以使用任意数量的variable元素。每一个variable元素都可以在layout中使用

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

在编译时检查Variable类型,因此如果一个Variable实现了Observable或observable collection,这应该反映在类型中。如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被观察(译者注:不会被观察,就是当变量的值改变,UI不会更新,后续会讲到这个Observable)

当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间不能有冲突的Variable定义。

产生的Binding类对于每一个Variables都有setter和getter方法。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。

4.3、自定义Binding类名称

默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及大写单词首字母,然后添加“Binding”后缀。这个类将被放置在一个module包里的databinding包下。

例如,所述layout文件contact_item.xml将生成ContactItemBinding。如果module包是com.example.my.app,那么它将被放置在com.example.my.app.databinding。

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

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

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

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

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

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

4.4、Includes

通过使用application namespace以及在属性中的Variable名字,从layout中传递Variables到一个 include 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

Data binding does not support include as a direct child of a merge element. For example, the following layout is not supported:

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

4.5 表达式

  • 常用表达式跟Java表达式很像,以下这些是一样的:

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

示例:

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

    • this
    • super
    • new
    • 显式泛型调用
  • Null合并操作

    • ?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android:text="@{user.displayName ?? user.lastName}"

这个操作符等价于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
  • 属性引用

第一个已经在前边提到了a)Data Binding表达式:JavaBean引用的简短格式。 
当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。

android:text="@{user.lastName}"
  • 避免 NullPointerException

Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{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="Map<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"]}'

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

android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
  • Resources 
    使用正常的表达式来访问resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式化字符串和复数可以通过提供参数来判断

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

当复数需要多个参数时,所有的参数都会通过:

Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"

一些资源需要显式类型判断:

类型 正常引用 表达式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

5、自动更新UI

Data Binding的真正作用是当数据变化时,可以通知给Data对象(layout中的data)。从而实现数据改变了,UI也会自动改变。

有三种不同的数据变化通知机制:

  • Observable对象、
  • ObservableFields
  • Observable collections。

第三方库,用于绑定AdapterView

  • binding-collection-adapter

5.1、Observable 对象

实现android.databinding.Observable接口的类,可以附加一个监听器到Bound对象以便监听对象上的所有属性的变化。

要实现 Observable Binding,首先得有一个 implement 了 android.databinding.Observable接口的类,为了方便,Android 提供了已经封装好的一个类 - BaseObservable,并且实现了监听器的注册机制。

在需要通知的属性的get方法上加上@Bindable,编译阶段会生成BR.[property name],只要调用notifyPropertyChanged()就可以刷新界面了。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getFirstName() {
       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);
   }
}

5.2、Observable 字段

如果只有少许变量,可以使用ObservableField,或者 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

ObservableFields是自包含具有单个字段的observable对象。要使用它需要在data对象中创建public final字段:

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

调用set方法时,Data Binding Library就会自动的帮我们通知界面刷新了。

user.firstName.set("Google");
int age = user.age.get();

5.3、Observable collections

一些app使用更多的动态结构来保存数据。在xml文件中,可以通过键值访问Observable集合。

ObservableArrayMap

当集合的key是引用类型,如String,可以使用ObservableArrayMap。

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

在layout文件中,通过String键可以访问map:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</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

ObservableArrayList用于键是整数:

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

在layout文件中,通过索引可以访问list:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList<Object>"/>
</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"/>

5.4、绑定AdapterView

在一个实际的项目中,相信AdapterView是使用得很多的,使用官方提供给的API来进行AdapterView的绑定需要写很多代码,使用起来不方便,但是由于Data Binding Library提供丰富的扩展功能,所以出现了很多第三方的库来扩展它,下面就来介绍一个比较好用的库binding-collection-adapter,Github地址:https://github.com/evant/binding-collection-adapter

5.4.1、添加依赖

使用的时候在你的build.gradle文件里面添加

compile 'me.tatarka:bindingcollectionadapter:0.16'

如果你要是用RecyclerView,还需要添加

compile 'me.tatarka:bindingcollectionadapter-recyclerview:0.16'

5.4.2、ViewModel的写法

下面就是ViewModel的写法:

您需要提供items和一个ItemBinding绑定到布局。 你应该使用ObservableList,如果有数据变化它会自动更新UI。 但是,如果你不需要该功能,可以使用其他的List。

public class ViewModel {
  public final ObservableList<String> items = new ObservableArrayList<>();
  public final ItemBinding<String> itemBinding = ItemBinding.of(BR.item, R.layout.item);
}

5.4.3、layout文件

然后使用app:items和app:itemBinding将其绑定到View。 还有一些便利方法可以通过app:layoutManager将LayoutManager附加到RecyclerView。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
      <import type="com.example.R" />
      <import type="me.tatarka.bindingcollectionadapter2.LayoutManagers" />
      <variable name="viewModel" type="com.example.ViewModel"/>
    </data>

    <ListView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <android.support.v7.widget.RecyclerView
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layoutManager="@{LayoutManagers.linear()}"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <android.support.v4.view.ViewPager
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"/>

    <Spinner
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:items="@{viewModel.items}"
      app:itemBinding="@{viewModel.itemBinding}"
      app:itemDropDownLayout="@{R.layout.item_dropdown}"/>
</layout>

然后是item layout:在item布局中,数据的集合将绑定到variable的name,就是传递到ItemBinding中的变量。

<!-- item.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="item" type="String"/> 
    </data>
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{item}"/>
</layout>

如果有多种样式的布局,那么就需要把ItemBinding换成OnItemBind,如下:

public final OnItemBind<String> onItemBind = new OnItemBind<String>() {
  @Override
  public void onItemBind(ItemBinding itemBinding, int position, String item) {
    itemBinding.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);
  }
};

更多用法,Github地址:https://github.com/evant/binding-collection-adapter

6、双向绑定

双向绑定就是在UI 发生变化,同步更新data中的变量

这里写图片描述

过去,我们需要自己定义Listener来做双向绑定:

<EditText android:text=“@{user.name}”
    android:afterTextChanged=“@{callback.change}”/>
  • 1
  • 2
public void change(Editable s) {
    final String text = s.toString();
    if (!text.equals(name.get()) {
        name.set(text);
    }
}

现在可以直接使用@= 来进行双向绑定了,使用起来十分简单

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textNoSuggestions"
    android:text="@={model.name}"/>

这样,我们对这个EditText的输入,就会自动set到对应model的name字段上。

自定义双向绑定

待续….

7、Binding 类

Binding 类是自动生成的,生成的Binding类链接了layout中variables与Views。如前面所讨论的,Binding的名称和包名可以定制。Binding类都扩展了android.databinding.ViewDataBinding。

7.1、创建

Binding类应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定一个layout。最常见的是在Binding类上使用静态方法.inflate()载入View层次结构。

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

如果使用不同的机制载入layout,可以分开绑定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

如果Binding的具体类型不知道,可以使用DataBindingUtil类来创建Binding:

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

7.2、带ID的Views

在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做一次遍历,提取带ID的Views。这种机制比起某些Views使用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>

它会生成如下的Binding类:

public final TextView firstName;
public final TextView lastName;

在databinding中,ID几乎没有必要,除非有一些实例需要从代码中访问Views。

7.3、Variables

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

它会在Binding中生成setters和getters:

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

7.4、ViewStubs

ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。

由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。

当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy必需监听ViewStub的OnInflateListener监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy允许开发者在其上设置一个OnInflateListener它会在建立Binding后调用。

7.5、Binding进阶

动态Variables

有时,不知道具体的Binding类,例如,一个RecyclerView适配器 对任意布局操作,不会知道具体的binding类。它仍然必需在onBindViewHolder期间赋值给Binding。

在这个例子中,该RecyclerView绑定的所有layouts有一个“item”的Variable。该BindingHolder有一个getBinding方法返回ViewDataBinding。

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

直接Binding

当一个variable或observable变化时,在下一帧之前binding将会改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()方法。

后台线程

只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。

8、属性Setters

每当绑定值更改时,生成的绑定类必须在具有绑定表达式的视图上调用setter方法。数据绑定框架有一些方法来定制调用哪个方法来设置值。

8.1、自动Setters

就像Data Binding会自动去查找get方法一下,在遇到属性绑定的时候,它也会去自动寻找对应的set方法。

例如,有关TextView的android:text属性的表达式会寻找一个setText(String)的方法。如果表达式返回一个int,Data Binding会搜索的setText(int)方法。注意:要表达式返回正确的类型,如果需要的话使用casting。

例如,DrawerLayout没有任何属性,但有大量的setters。您可以使用自动setters来使用其中的一个。下面的例子中:由于存在setDrawerListener()方法,所以可以使用app:drawerListener这样的属性

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

8.2、重命名的Setters

有的setters方法和属性名称并不匹配。对于这些方法,通过BindingMethods注解来与xml中的属性关联。

例如,android:tint属性与setImageTintList相关联,而不与setTint相关。

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

以上例子,开发者需要重命名setters是不太可能了,android架构属性已经实现了。

8.3、自定义Setters

有些xml属性需要自定义它对应的方法。

例如,对于android:paddingLeft属性并没有相关setter。但是setPadding(left, top, right, bottom)是存在在。一个带有BindingAdapter注解的静态绑定适配器方法,可以为一个xml属性自定义setter方法。

Android的属性已经创造了Binding Adapters。举例来说,对于paddingLeft:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

Binding适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。

当有冲突时,自定义的Binding适配器将覆盖Data Binding默认适配器。

也可以创建接收多个参数的适配器。

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>

如果对于一个ImageViewimageUrl和error都被使用并且imageUrl是一个string类型以及error是一个drawable时,该适配器会被调用。

  • 匹配的过程中自定义namespaces将被忽略。
  • 你也可以为Android namespaces写适配器。

绑定适配器方法可以在其处理程序中采用旧值。 采用旧值和新值的方法,应该首先具有属性的所有旧值,然后是新值:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

事件处理程序只能与一个抽象方法接口或抽象类一起使用。 例如:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

9、转换

9.1、对象转换

当从Binding表达式返回一个对象,一个setter会从自动的、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。

这是为了方便那些使用ObservableMaps来保存数据。例如:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

在userMap返回一个对象并且该对象将自动转换为setText(CharSequence)的参数类型。当有关参数类型可能混乱时,开发人员需要在表达式中转换。

9.2、自定义转换

有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

这里,背景需要Drawable对象,但颜色是一个整数。不管何时有Drawable并且返回值是一个整数,那么整数类型会被转换为ColorDrawable。这个转换是通过使用带有BindingConversion注解的静态方法完成的:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

注意:转换仅仅发生在setter级别,因此它是不允许以下混合类型:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

10、表达式链

10.1、重复的表达式

<ImageView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<CheckBox android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

可以简化为:

<ImageView android:id=“@+id/avatar”
 android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{avatar.visibility}”/>
<CheckBox android:visibility="@{avatar.visibility}"/>

10.2、隐式更新

<CheckBox android:id=”@+id/seeAds“/>
<ImageView android:visibility=“@{seeAds.checked ?
  View.VISIBLE : View.GONE}”/>

这样CheckBox的状态变更后ImageView会自动改变visibility。

                                                                    
                                                                           全栈开发者微信公众号 

猜你喜欢

转载自blog.csdn.net/qiujiaxin050/article/details/80852344