Data Binding 从入门到精通

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

虽然官方给出了教程,Data Binding Library(Android 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元素以及一个布局。这个布局就是你之前没有使用Data Binding的布局。举例说明如下:



   
       
   
   
       
       
   

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


在layout的属性表达式写作@{},下面是一个TextView的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());

如果你在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。处理事件有两种方法:

  • Method References
  • Listener Bindings

4、深入了解Layout文件

4.1、Import

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


    

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


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



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


    
    
    
    
 

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


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


    
    

就像在Java中,java.lang.*是自动导入的。

4.2、Variables

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


    
    
    
    

在编译时检查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属性来重命名或放置在不同的包中。例如:


    ...

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


    ...

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


    ...

4.4、Includes

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



   
       
   
   
       
       
   

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,为了简便都可以使用[]来访问。

  
  
  
  
  
  
  
  

…
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 firstName =
       new ObservableField<>();
   public final ObservableField 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 user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

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


    
    


ObservableArrayList

ObservableArrayList用于键是整数:

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

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


    
    
    


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 items = new ObservableArrayList<>();
  public final ItemBinding itemBinding = ItemBinding.of(BR.item, R.layout.item);
}

5.4.3、layout文件

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


    
      
      
      
    

    

    

    

    


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



    
         
    
    

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

public final OnItemBind onItemBind = new OnItemBind() {
  @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中的变量

Data Binding 从入门到精通_第1张图片

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


public void change(Editable s) {
    final String text = s.toString();
    if (!text.equals(name.get()) {
        name.set(text);
    }
}

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


这样,我们对这个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还要快。例如:


   
       
   
   
       
       
   

它会生成如下的Binding类:

public final TextView firstName;
public final TextView lastName;

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

7.3、Variables

每个Variable都有访问方法。


    
    
    
    

它会在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这样的属性


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

如果对于一个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来保存数据。例如:


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

9.2、自定义转换

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


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

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

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


10、表达式链

10.1、重复的表达式




可以简化为:




10.2、隐式更新



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

参考:

Android Data Binding(数据绑定)用户指南
QQ 音乐 Android 团队分享 Android DataBinding 数据绑定
Android,DataBinding的官方双向绑定
Android DataBinding 双向绑定
从零开始的Android新项目8 - Data Binding高级篇
棉花糖给 Android 带来的 Data Bindings(数据绑定库)

你可能感兴趣的:(Data,Binding)