GVKun编程网logo

数据绑定(三)为Binding指定绑定源的几种方法(绑定数据源的步骤)

21

在这里,我们将给大家分享关于数据绑定的知识,让您更了解三为Binding指定绑定源的几种方法的本质,同时也会涉及到如何更有效地AndroidDataBinding(数据绑定)用户指南、AndroidJ

在这里,我们将给大家分享关于数据绑定的知识,让您更了解三为Binding指定绑定源的几种方法的本质,同时也会涉及到如何更有效地Android Data Binding(数据绑定)用户指南、Android Jetpack中DataBinding数据绑定布局和绑定表达式(三)、Android 官方数据绑定框架 DataBinding(一)、Android官方数据绑定框架DataBinding(三)的内容。

本文目录一览:

数据绑定(三)为Binding指定绑定源的几种方法(绑定数据源的步骤)

数据绑定(三)为Binding指定绑定源的几种方法(绑定数据源的步骤)

原文: 数据绑定(三)为Binding指定绑定源的几种方法

Binding的源是数据的来源,所以,只要一个对象包含数据并能通过属性把数据暴露出来,它就能当作Binding的源来使用,常用的办法有:

一。把普通CLR类型单个对象指定为Source

如果类型实现了INotifyPropertyChanged接口,则可通过在属性的set语句中激发PropertyChanged事件来通知Binding数据已被更新

二。把普通CLR集合类型对象指定为Source

一般是把控件的ItemsSource属性使用Binding关联到一个集合对象上,即为ItemsSource属性直接赋一个集合对象

三。把ADO.NET数据对象指定为Source

四。使用XmlDataProvider把XML数据指定为Source

五。把依赖对象指定为Source

六。把容器的DataContext指定为Source

七。通过ElementName指定Source

八。通过Binding的RelativeSource属性相对的指定Source

当控件需要关注自己的、自己容器的或者自己内部元素的某个值就需要使用这种办法

九。把ObjectDataProvider对象指定为Source

当数据源的数据不是通过属性而是通过方法暴露给外界的时候,可以使用这两种对象来包装数据源再把它们指定为Source

十。使用LINQ检索的到的数据对象作为Binding的Source

Android Data Binding(数据绑定)用户指南

Android Data Binding(数据绑定)用户指南

1)介绍

这篇文章介绍了如何使用Data Binding库来写声明的layouts文件,并且用最少的代码来绑定你的app逻辑和layouts文件。

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

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

2)构建环境

要开始使用Data Binding,首先需要在Android SDK Manager的支持库里下载该库。

你的app要使用Data Binding,需要添加Data Binding到gradle构建文件里,如下:

 
  1. android {

  2. ....

  3. dataBinding {

  4. enabled = true

  5. }

  6. }

Data Binding插件将会在你的项目内添加必需提供的以及编译配置依赖。

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

3)Data Binding Layout文件

a)Data Binding表达式

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

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <layout xmlns:android="http://schemas.android.com/apk/res/android">

  3. <data>

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

  5. </data>

  6. <LinearLayout

  7. android:orientation="vertical"

  8. android:layout_width="match_parent"

  9. android:layout_height="match_parent">

  10. <TextView android:layout_width="wrap_content"

  11. android:layout_height="wrap_content"

  12. android:text="@{user.firstName}"/>

  13. <TextView android:layout_width="wrap_content"

  14. android:layout_height="wrap_content"

  15. android:text="@{user.lastName}"/>

  16. </LinearLayout>

  17. </layout>

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

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

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

 
  1. <TextView android:layout_width="wrap_content"

  2. android:layout_height="wrap_content"

  3. android:text="@{user.firstName}"/>

b)Data对象

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

 
  1. public class User {

  2. public final String firstName;

  3. public final String lastName;

  4. public User(String firstName, String lastName) {

  5. this.firstName = firstName;

  6. this.lastName = lastName;

  7. }

  8. }

  9.  

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

 
  1. public class User {

  2. private final String firstName;

  3. private final String lastName;

  4. public User(String firstName, String lastName) {

  5. this.firstName = firstName;

  6. this.lastName = lastName;

  7. }

  8. public String getFirstName() {

  9. return this.firstName;

  10. }

  11. public String getLastName() {

  12. return this.lastName;

  13. }

  14. }

  15.  

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

c)Binding数据

默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”后缀。上述的layout文件是main_activity.xml,因此生成的类名是MainActivityBinding。此类包含从layout属性到layout的Views中所有的bindings(例如user变量),并且它还知道如何给Binding表达式分配数值。创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:

 
  1. @Override

  2. protected void onCreate(Bundle savedInstanceState) {

  3. super.onCreate(savedInstanceState);

  4. MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);

  5. User user = new User("Test", "User");

  6. binding.setUser(user);

  7. }

  8.  

就是这样,运行app后,你将会看到Test User。或者你可以通过如下获取View:

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

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

 
  1. ListItembinding binding = ListItembinding.inflate(layoutInflater, viewGroup,

  2. false);

  3. //or

  4. ListItembinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

  5.  

d)事件处理

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

  • Method References
  • Listener Bindings

4)深入Layout文件

a)Import

零个或多个import元素可能在data元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:

 
  1. <data>

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

  3. </data>

  4.  

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

 
  1. <TextView

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

  3. android:layout_width="wrap_content"

  4. android:layout_height="wrap_content"

  5. android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

当类名有冲突时,其中一个类名可以重命名为alias:

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

  2. <import type="com.example.real.estate.View"

  3. alias="Vista"/>

这样,在该layout文件中Vista对应com.example.real.estate.View,而View对应android.view.View。导入的类型可以在Variable和表达式中使用作为引用来使用:

 
  1. <data>

  2. <import type="com.example.User"/>

  3. <import type="java.util.List"/>

  4. <variable name="user" type="User"/>

  5. <variable name="userList" type="List<User>"/>

  6. </data>

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

 
  1. <TextView

  2. android:text="@{((User)(user.connection)).lastName}"

  3. android:layout_width="wrap_content"

  4. android:layout_height="wrap_content"/>

导入的类型还可以在表达式中使用static属性和方法:

 
  1. <data>

  2. <import type="com.example.MyStringUtils"/>

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

  4. </data>

  5. <TextView

  6. android:text="@{MyStringUtils.capitalize(user.lastName)}"

  7. android:layout_width="wrap_content"

  8. android:layout_height="wrap_content"/>

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

b)Variables

data中可以使用任意数量的variable元素。每一个variable元素描述了一个用于layout文件中Binding表达式的属性。

 
  1. <data>

  2. <import type="android.graphics.drawable.Drawable"/>

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

  4. <variable name="image" type="Drawable"/>

  5. <variable name="note" type="String"/>

  6. </data>

Variable类型在编译时检查,因此如果一个Variable实现了Observable或observable collection,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!

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

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

c)自定义Binding类名称

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

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

 
  1. <data>

  2. ...

  3. </data>

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

 
  1. <data>

  2. ...

  3. </data>

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

 
  1. <data>

  2. ...

  3. </data>

d)Includes

通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <layout xmlns:android="http://schemas.android.com/apk/res/android"

  3. xmlns:bind="http://schemas.android.com/apk/res-auto">

  4. <data>

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

  6. </data>

  7. <LinearLayout

  8. android:orientation="vertical"

  9. android:layout_width="match_parent"

  10. android:layout_height="match_parent">

  11. <include layout="@layout/name"

  12. bind:user="@{user}"/>

  13. <include layout="@layout/contact"

  14. bind:user="@{user}"/>

  15. </LinearLayout>

  16. </layout>

注意:在name.xml以及contact.xml两个layout文件中必需要有user variable

e)表达式

  • 常用表达式跟Java表达式很像,以下这些是一样的:
    • 数学 + - / * %

    • 字符串连接 +

    • 逻辑 && ||

    • 二进制 & | ^

    • 一元运算 + - ! ~

    • 移位 >> >>> <<

    • 比较 == > < >= <=

    • instanceof

    • 分组 ()

    • null

    • Cast

    • 方法调用

    • 数据访问 []

    • 三元运算 ?:

    • 示例:

 
  1. android:text="@{String.valueOf(index + 1)}"

  2. android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"

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

 
  1. <data>

  2. <import type="android.util.SparseArray"/>

  3. <import type="java.util.Map"/>

  4. <import type="java.util.List"/>

  5. <variable name="list" type="List<String>"/>

  6. <variable name="sparse" type="SparseArray<String>"/>

  7. <variable name="map" type="Map<String, String>"/>

  8. <variable name="index" type="int"/>

  9. <variable name="key" type="String"/>

  10. </data>

  11. android:text="@{list[index]}"

  12. android:text="@{sparse[index]}"

  13. android:text="@{map[key]}"

  • 字符串
    当使用单引号包含属性值时,在表达式中使用双引号很容易:
android:text='@{map["firstName"]}'

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

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

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

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

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

 
  1. android:text="@{@string/nameFormat(firstName, lastName)}"

  2. android:text="@{@plurals/banana(bananaCount)}"

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

 
  1. Have an orange

  2. Have %d oranges

  3. 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)Data 对象

任何Plain old Java object(PO​​JO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable collections

当这些可观察Data对象​​绑定到UI,Data对象属性的更改后,UI也将自动更新。

a)Observable 对象

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

Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaSEObservable的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable注解给getter以及setter内通知来完成的。

 
  1. private static class User extends BaSEObservable {

  2. private String firstName;

  3. private String lastName;

  4. @Bindable

  5. public String getFirstName() {

  6. return this.firstName;

  7. }

  8. @Bindable

  9. public String getFirstName() {

  10. return this.lastName;

  11. }

  12. public void setFirstName(String firstName) {

  13. this.firstName = firstName;

  14. notifyPropertyChanged(BR.firstName);

  15. }

  16. public void setLastName(String lastName) {

  17. this.lastName = lastName;

  18. notifyPropertyChanged(BR.lastName);

  19. }

  20. }

  21.  

在编译期间,Bindable注解在BR类文件中生成一个Entry。BR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable接口通过方便的PropertyChangeRegistry来实现用于储存和有效地通知监听器。

b)Observable 字段

一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFieldsObservableFields是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:

 
  1. private static class User {

  2. public final ObservableField<String> firstName =

  3. new ObservableField<>();

  4. public final ObservableField<String> lastName =

  5. new ObservableField<>();

  6. public final ObservableInt age = new ObservableInt();

  7. }

  8.  

就是这样,要访问该值,使用set和get方法:

 
  1. user.firstName.set("Google");

  2. int age = user.age.get();

  3.  

c)Observable 集合

一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap用于键是引用类型,如String

 
  1. ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();

  2. user.put("firstName", "Google");

  3. user.put("lastName", "Inc.");

  4. user.put("age", 17);

  5.  

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

 
  1. <data>

  2. <import type="android.databinding.ObservableMap"/>

  3. <variable name="user" type="ObservableMap<String, Object>"/>

  4. </data>

  5. <TextView

  6. android:text='@{user["lastName"]}'

  7. android:layout_width="wrap_content"

  8. android:layout_height="wrap_content"/>

  9. <TextView

  10. android:text='@{String.valueOf(1 + (Integer)user["age"])}'

  11. android:layout_width="wrap_content"

  12. android:layout_height="wrap_content"/>

ObservableArrayList用于键是整数:

 
  1. ObservableArrayList<Object> user = new ObservableArrayList<>();

  2. user.add("Google");

  3. user.add("Inc.");

  4. user.add(17);

  5.  

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

 
  1. <data>

  2. <import type="android.databinding.ObservableList"/>

  3. <import type="com.example.my.app.Fields"/>

  4. <variable name="user" type="ObservableList<Object>"/>

  5. </data>

  6. <TextView

  7. android:text='@{user[Fields.LAST_NAME]}'

  8. android:layout_width="wrap_content"

  9. android:layout_height="wrap_content"/>

  10. <TextView

  11. android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'

  12. android:layout_width="wrap_content"

  13. android:layout_height="wrap_content"/>

6)Binding生成

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

a)创建

Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater还有一个是采用ViewGroup

 
  1. MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);

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

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

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil类来创建Binding:

 
  1. ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,

  2. parent, attachToParent);

  3. ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

  4.  

b)带ID的Views

在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById还要快。例如:

 
  1. <layout xmlns:android="http://schemas.android.com/apk/res/android">

  2. <data>

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

  4. </data>

  5. <LinearLayout

  6. android:orientation="vertical"

  7. android:layout_width="match_parent"

  8. android:layout_height="match_parent">

  9. <TextView android:layout_width="wrap_content"

  10. android:layout_height="wrap_content"

  11. android:text="@{user.firstName}"

  12. android:id="@+id/firstName"/>

  13. <TextView android:layout_width="wrap_content"

  14. android:layout_height="wrap_content"

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

  16. android:id="@+id/lastName"/>

  17. </LinearLayout>

  18. </layout>

它会生成如下的Binding类:

 
  1. public final TextView firstName;

  2. public final TextView lastName;

  3.  

IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。

c)Variables

每个Variable会有访问方法。

 
  1. <data>

  2. <import type="android.graphics.drawable.Drawable"/>

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

  4. <variable name="image" type="Drawable"/>

  5. <variable name="note" type="String"/>

  6. </data>

它会在Binding中生成setters和getters:

 
  1. public abstract com.example.User getUser();

  2. public abstract void setUser(com.example.User user);

  3. public abstract Drawable getimage();

  4. public abstract void setimage(Drawable image);

  5. public abstract String getNote();

  6. public abstract void setNote(String note);

  7.  

d)ViewStubs

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

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

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

e)Binding进阶

  • 动态Variables

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

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

 
  1. public void onBindViewHolder(BindingHolder holder, int position) {

  2. final T item = mItems.get(position);

  3. holder.getBinding().setvariable(BR.item, item);

  4. holder.getBinding().executePendingBindings();

  5. }

  6.  
  • 直接Binding

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

  • 后台线程

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

7)属性Setters

每当绑定值的变化,生成的Binding类必须调用setter方法​​。Data Binding框架有可以自定义赋值的方法。

a)自动Setters

对于一个属性,Data Binding试图找到setAttribute方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。

例如,有关TextView的android:text属性的表达式会寻找一个setText(String)的方法。如果表达式返回一个int,Data Binding会搜索的setText(int)方法。注意:要表达式返回正确的类型,如果需要的话使用casting。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。

 
  1. <android.support.v4.widget.DrawerLayout

  2. android:layout_width="wrap_content"

  3. android:layout_height="wrap_content"

  4. app:scrimColor="@{@color/scrim}"

  5. app:drawerListener="@{fragment.drawerListener}"/>

b)重命名的Setters

一些有setters的属性按名称并不匹配。对于这些方法,属性可以通过BindingMethods注解相关联。这必须与一个包含BindingMethod注解的类相关联,每一个用于一个重命名的方法。例如,android:tint属性与setimageTintList相关联,而不与setTint相关。

 
  1. @BindingMethods({

  2. @BindingMethod(type = "android.widget.ImageView",

  3. attribute = "android:tint",

  4. method = "setimageTintList"),

  5. })

  6.  

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

c)自定义Setters

有些属性需要自定义绑定逻辑。例如,对于android:paddingLeft属性并没有相关setter。相反,setPadding(left, top, right, bottom)是存在在。一个带有BindingAdapter注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。

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

 
  1. @BindingAdapter("android:paddingLeft")

  2. public static void setPaddingLeft(View view, int padding) {

  3. view.setPadding(padding,

  4. view.getPaddingTop(),

  5. view.getPaddingRight(),

  6. view.getPaddingBottom());

  7. }

  8.  

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

当有冲突时,开发人员创建的Binding适配器将覆盖Data Binding默认适配器。

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

 
  1. @BindingAdapter({"bind:imageUrl", "bind:error"})

  2. public static void loadImage(ImageView view, String url, Drawable error) {

  3. Picasso.with(view.getContext()).load(url).error(error).into(view);

  4. }

  5.  
 
  1. <ImageView app:imageUrl=“@{venue.imageUrl}”

  2. app:error=“@{@drawable/venueError}”/>

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

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

8)转换

a)对象转换

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

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

 
  1. <TextView

  2. android:text='@{userMap["lastName"]}'

  3. android:layout_width="wrap_content"

  4. android:layout_height="wrap_content"/>

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

b)自定义转换

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

 
  1. <View

  2. android:background="@{isError ? @color/red : @color/white}"

  3. android:layout_width="wrap_content"

  4. android:layout_height="wrap_content"/>

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

 
  1. @BindingConversion

  2. public static ColorDrawable convertColorToDrawable(int color) {

  3. return new ColorDrawable(color);

  4. }

  5.  

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

 
  1. <View

  2. android:background="@{isError ? @drawable/error : @color/white}"

  3. android:layout_width="wrap_content"

  4. android:layout_height="wrap_content"/>

9)Android Studio支持

Android Studio为数据绑定支持许多的代码编辑。例如,它支持以下功能:

  • 语法高亮
  • 标记表达式的语法错误
  • XML代码补全
  • 引用,包括navigation(如导航到声明处)以及快速文档查询

注意: 数组以及通用类型,比如说Observable类,可能会显示错误事实上并没有错误。

预览面板会显示数据绑定的默认值。在以下例子中,面板会在TextView中显示PLACEHOLDER默认值

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

如果你需要在设计阶段就显示默认值,你可以使用工具属性来代替默认表达数值,参考:Designtime Layout Attributes

---------------------<完>-----------------------

Android Jetpack中DataBinding数据绑定布局和绑定表达式(三)

Android Jetpack中DataBinding数据绑定布局和绑定表达式(三)

Android Jetpack中DataBinding数据绑定布局和绑定表达式(三)

  • 布局和绑定表达式
  • 数据对象
  • 绑定数据
  • 表达式语言
  • 缺少的运算
  • Null 合并运算符
  • 属性引用
  • 避免出现 Null 指针异常
  • 视图引用
  • 集合
  • 字符串字面量
  • 资源
  • 事件处理
  • 方法引用
  • 避免使用复杂的监听器
  • 导入、变量和包含
  • 导入
  • 类型别名
  • 导入其他类
  • 变量
  • 包含


布局和绑定表达式

借助表达式语言,您可以编写表达式来处理视图分派的事件。数据绑定库会自动生成将布局中的视图与您的数据对象绑定所需的类。

数据绑定布局文件略有不同,以根标记 layout 开头,后跟 data 元素和 view 根元素。此视图元素是非绑定布局文件中的根。以下代码展示了示例布局文件:

<?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 变量描述了可在此布局中使用的属性。

<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}" />

注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。

数据对象

现在我们假设您有一个 plain-old 对象来描述 User 实体:

Kotlin

    data class User(val firstName: String, val lastName: String)

Java


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

此类型的对象拥有永不改变的数据。应用包含读取一次后不会再发生改变的数据是很常见的。也可以使用遵循一组约定的对象,例如 Java 中的访问器方法的使用,如以下示例所示:

Kotlin

    // Not applicable in Kotlin.
    data class User(val firstName: String, val lastName: String)

Java

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

从数据绑定的角度来看,这两个类是等效的。用于 android:text 特性的表达式 @{user.firstName} 访问前一个类中的 firstName 字段和后一个类中的 getFirstName() 方法。或者,如果该方法存在,也将解析为 firstName()。

绑定数据

系统会为每个布局文件生成一个绑定类。默认情况下,类名称基于布局文件的名称,它会转换为 Pascal 大小写形式并在末尾添加 Binding 后缀。以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding。此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)

        binding.user = User("Test", "User")
    }

Java

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

在运行时,应用会在界面中显示 Test 用户。或者,您可以使用 LayoutInflater 获取视图,如以下示例所示:

Kotlin

    val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

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

如果您要在 Fragment、ListView 或 RecyclerView 适配器中使用数据绑定项,您可能更愿意使用绑定类或 DataBindingUtil 类的 inflate() 方法,如以下代码示例所示:

Kotlin

    val listItembinding = ListItembinding.inflate(layoutInflater, viewGroup, false)
    // or
    val listItembinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

Java

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

表达式语言

常见功能
表达式语言与托管代码中的表达式非常相似。您可以在表达式语言中使用以下运算符和关键字:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:
    示例:
android:text="@{String.valueOf(index + 1)}"
    android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
    android:transitionName='@{"image_" + id}'

缺少的运算

您可以在托管代码中使用的表达式语法中缺少以下运算:

  • this
  • super
  • new
  • 显式泛型调用

Null 合并运算符

如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。

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

这在功能上等效于:

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

属性引用

表达式可以使用以下格式在类中引用属性,这对于字段、getter 和 ObservableField 对象都一样:

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

避免出现 Null 指针异常

生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。

视图引用

表达式可以通过以下语法按 ID 引用布局中的其他视图:

android:text="@{exampleText.text}"

注意:绑定类将 ID 转换为驼峰式大小写。

在以下示例中,TextView 视图引用同一布局中的 EditText 视图:

<EditText
        android:id="@+id/example_text"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
    <TextView
        android:id="@+id/example_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{exampleText.text}"/>

集合

为方便起见,可使用 [] 运算符访问常见集合,例如数组、列表、稀疏列表和映射。

<data>
        <import type="android.util.SparseArray"/>
        <import type="java.util.Map"/>
        <import type="java.util.List"/>
        <variable name="list" type="List&lt;String>"/>
        <variable name="sparse" type="SparseArray&lt;String>"/>
        <variable name="map" type="Map&lt;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]}" 

注意:要使 XML 不含语法错误,您必须转义 < 字符。例如:不要写成 List 形式,而是必须写成 List<String>。

您还可以使用 object.key 表示法在映射中引用值。例如,以上示例中的 @{map[key]} 可替换为 @{map.key}。

字符串字面量

您可以使用单引号括住特性值,这样就可以在表达式中使用双引号,如以下示例所示:

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

也可以使用双引号括住特性值。如果这样做,则还应使用反单引号 ` 将字符串字面量括起来:

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

资源

表达式可以使用以下语法引用应用资源:

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

您可以通过提供参数来评估格式字符串和复数形式:

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

您可以将属性引用和视图引用作为资源参数进行传递:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}" 

当一个复数带有多个参数时,您必须传递所有参数:


      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

事件处理

通过数据绑定,您可以编写从视图分派的表达式处理事件(例如,onClick() 方法)。事件特性名称由监听器方法的名称确定,但有一些例外情况。例如,View.OnClickListener 有一个 onClick() 方法,所以该事件的特性为 android:onClick。

有一些专门针对点击事件的事件处理脚本,这些处理脚本需要使用除 android:onClick 以外的特性来避免冲突。您可以使用以下属性来避免这些类型的冲突:

监听器 setter属性
SearchViewsetonSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetonZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetonZoomOutClickListener(View.OnClickListener)android:onZoomOut

您可以使用以下机制处理事件:

  • 方法引用:在表达式中,您可以引用符合监听器方法签名的方法。当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。
  • 监听器绑定:这些是在事件发生时进行求值的 lambda 表达式。数据绑定始终会创建一个要在视图上设置的监听器。事件被分派后,监听器会对 lambda 表达式进行求值。

方法引用

事件可以直接绑定到处理脚本方法,类似于为 Activity 中的方法指定 android:onClick 的方式。与 View onClick 特性相比,一个主要优点是表达式在编译时进行处理,因此,如果该方法不存在或其签名不正确,则会收到编译时错误。

方法引用和监听器绑定之间的主要区别在于实际监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果您希望在事件发生时对表达式求值,则应使用监听器绑定。

要将事件分配给其处理脚本,请使用常规绑定表达式,并以要调用的方法名称作为值。例如,请考虑以下布局数据对象示例:

Kotlin

   class MyHandlers {
       fun onClickFriend(view: View) { ... }
   }  

Java

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

绑定表达式可将视图的点击监听器分配给 onClickFriend() 方法,如下所示:

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.MyHandlers"/>
           <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="@{handlers::onClickFriend}"/>
       </LinearLayout>
    </layout> 

注意:表达式中的方法签名必须与监听器对象中的方法签名完全一致。
监听器绑定
监听器绑定是在事件发生时运行的绑定表达式。它们类似于方法引用,但允许您运行任意数据绑定表达式。此功能适用于 Gradle 2.0 版及更高版本的 Android Gradle 插件。

在方法引用中,方法的参数必须与事件监听器的参数匹配。在监听器绑定中,只有您的返回值必须与监听器的预期返回值相匹配(预期返回值无效除外)。例如,请参考以下具有 onSaveClick() 方法的 presenter 类:

Kotlin

    class Presenter {
        fun onSaveClick(task: Task){}
    } 

Java

    public class Presenter {
        public void onSaveClick(Task task){}
    } 

然后,您可以将点击事件绑定到 onSaveClick() 方法,如下所示:

<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable name="task" type="com.android.example.Task" />
            <variable name="presenter" type="com.android.example.Presenter" />
        </data>
        <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onSaveClick(task)}" />
        </LinearLayout>
    </layout> 

在表达式中使用回调时,数据绑定会自动为事件创建并注册必要的监听器。当视图触发事件时,数据绑定会对给定表达式求值。与常规绑定表达式一样,在对这些监听器表达式求值时,仍会获得数据绑定的 Null 值和线程安全。

在上面的示例中,我们尚未定义传递给 onClick(View) 的 view 参数。监听器绑定提供两个监听器参数选项:您可以忽略方法的所有参数,也可以命名所有参数。如果您想命名参数,则可以在表达式中使用这些参数。例如,上面的表达式可以写成如下形式:

android:onClick="@{(view) -> presenter.onSaveClick(task)}" 

或者,如果您想在表达式中使用参数,则采用如下形式:

Kotlin

    class Presenter {
        fun onSaveClick(view: View, task: Task){}
    } 

Java

    public class Presenter {
        public void onSaveClick(View view, Task task){}
    } 
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}" 

您可以在 lambda 表达式中使用多个参数:

Kotlin

    class Presenter {
        fun onCompletedChanged(task: Task, completed: Boolean){}
    }

    

Java

    public class Presenter {
        public void onCompletedChanged(Task task, boolean completed){}
    } 

Xml使用

<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
    

如果您监听的事件返回类型不是 void 的值,则您的表达式也必须返回相同类型的值。例如,如果要监听长按事件,表达式应返回一个布尔值。

Kotlin

    class Presenter {
        fun onLongClick(view: View, task: Task): Boolean { }
    } 

Java

    public class Presenter {
        public boolean onLongClick(View view, Task task) { }
    } 

Xml使用

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}" 

如果由于 null 对象而无法对表达式求值,则数据绑定将返回该类型的默认值。例如,引用类型返回 null,int 返回 0,boolean 返回 false,等等。

如果您需要将表达式与谓词(例如,三元运算符)结合使用,则可以使用 void 作为符号。

xml

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

避免使用复杂的监听器

监听器表达式功能非常强大,可以使您的代码非常易于阅读。另一方面,包含复杂表达式的监听器会使您的布局难以阅读和维护。这些表达式应该像将可用数据从界面传递到回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。

导入、变量和包含

数据绑定库提供了诸如导入、变量和包含等功能。通过导入功能,您可以轻松地在布局文件中引用类。通过变量功能,您可以描述可在绑定表达式中使用的属性。通过包含功能,您可以在整个应用中重复使用复杂的布局。

导入

通过导入功能,您可以轻松地在布局文件中引用类,就像在托管代码中一样。您可以在 data 元素使用多个 import 元素,也可以不使用。以下代码示例将 View 类导入到布局文件中:

Xml

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

导入 View 类可让您通过绑定表达式引用该类。以下示例展示了如何引用 View 类的 VISIBLE 和 GONE 常量:

Xml

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

类型别名

当类名有冲突时,其中一个类可使用别名重命名。以下示例将 com.example.real.estate 软件包中的 View 类重命名为 Vista:

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

您可以在布局文件中使用 Vista 引用 com.example.real.estate.View,使用 View 引用 android.view.View。

导入其他类

导入的类型可用作变量和表达式中的类型引用。以下示例显示了用作变量类型的 User 和 List:

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

注意:Android Studio 尚不处理导入,因此导入变量的自动填充功能可能无法在您的 IDE 中使用。您的应用仍可以编译,并且您可以通过在变量定义中使用完全限定名称来解决这个 IDE 问题。

您还可以使用导入的类型来对表达式的一部分进行类型转换。以下示例将 connection 属性强制转换为类型 User:

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

在表达式中引用静态字段和方法时,也可以使用导入的类型。以下代码会导入 MyStringUtils 类,并引用其 capitalize 方法:

<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.lang.*。

变量

您可以在 data 元素中使用多个 variable 元素。每个 variable 元素都描述了一个可以在布局上设置、并将在布局文件中的绑定表达式中使用的属性。以下示例声明了 user、image 和 note 变量:

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

变量类型在编译时进行检查,因此,如果变量实现 Observable 或者是可观察集合,则应反映在类型中。如果该变量是不实现 Observable 接口的基类或接口,则变量是“不可观察的”。

如果不同配置(例如横向或纵向)有不同的布局文件,则变量会合并在一起。这些布局文件之间不得存在有冲突的变量定义。

在生成的绑定类中,每个描述的变量都有一个对应的 setter 和 getter。在调用 setter 之前,这些变量一直采用默认的托管代码值,例如引用类型采用 null,int 采用 0,boolean 采用 false,等等。

系统会根据需要生成名为 context 的特殊变量,用于绑定表达式。context 的值是根视图的 getContext() 方法中的 Context 对象。context 变量会被具有该名称的显式变量声明替换。

包含

通过使用应用命名空间和特性中的变量名称,变量可以从包含的布局传递到被包含布局的绑定。以下示例展示了来自 name.xml 和 contact.xml 布局文件的被包含 user 变量:

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

数据绑定不支持 include 作为 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><!-- Doesn't work -->
           <include layout="@layout/name"
               bind:user="@{user}"/>
           <include layout="@layout/contact"
               bind:user="@{user}"/>
       </merge>
    </layout> 

Android 官方数据绑定框架 DataBinding(一)

Android 官方数据绑定框架 DataBinding(一)

今天来了解一下 android 最新给我们带来的数据绑定框架 ——Data Binding Library。数据绑定框架给我们带来了更大的方便性,以前我们可能需要在 Activity 里写很多的 findViewById,烦人的代码也增加了我们代码的耦合性,现在我们马上就可以抛弃那么多findViewById。说到这里,有人可能会有个疑问:我使用一些注解框架也可以不用 findViewById 啊,是的,但是注解注定要拖慢我们代码的速度,Data Binding 则不会,官网文档说还会提高解析 XML 的速度,最主要的 Data Binding 并不是单单减少了我们的 findViewById,更多好处请往下看文章。

一、环境 
在开始使用新东西之前,我们需要稍微的配置一下环境,这里要求你的 Android Studio 版本是 1.3+,使用 eclipse 的同学暂时还没有办法使用该框架,请换用 Android Studio。还有,在开始之前,请更新你的 Support repository 到最新的版本。 
万事俱备,那我们就开始搭配环境!

新建一个 project,在 dependencies 中添加以下依赖

classpath "com.android.databinding:dataBinder:1.0-rc1"

新建 module,并且在 module 的 build.gradle 文件中添加

apply plugin: ''com.android.application''  
apply plugin: ''com.android.databinding''

ok,到现在为止,我们的环境就准备完毕了,下面我们就开始 Data Binding 的学习啦。

二、Data Binding 尝试 
在代码开始,我们并不直接进入新东西的讲解,而且以一段代码展现 Data Binding 的魅力。 
首先我们需要一个 java bean,很简单,一个学生类。

public class Student {  
    private String name;  
    private String addr;  
  
    public Student() {  
    }  
  
    public Student(String name, String addr) {  
        this.name = name;  
        this.addr = addr;  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getAddr() {  
        return this.addr;  
    }  
  
    public void setAddr(String addr) {  
        this.addr = addr;  
    }  
}

再来看看我们布局文件怎么写:

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <variable  
            name="stu"  
            type="org.loader.androiddatabinding.Student" />  
    </data>  
  
    <LinearLayout  
        android:orientation="vertical"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content">  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{stu.name}"/>  
  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{stu.addr}"/>  
    </LinearLayout>  
</layout>

可以看到我们的 xml 布局和以前还有有一定的差别的,但是差别也不是很大。 
最后来看看Activity怎么写。

public class MainActivity extends AppCompatActivity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);  
        binding.setStu(new Student("loader""山东莱芜"));  
    }  
}

Activity 的代码非常简单,就添加了两行代码,而且,值得注意的是:我们并没有 findViewById 然后再去 setText。 
这段小代码运行的结果大家可能已经猜到了,就是在界面上显示 loader山东莱芜两句话。

在看完小实例后,大家是不是感觉棒棒哒? 没有了之前的 find 控件,没有了 setText,Activity 代码更加简洁明了! 
下面开始,我们进入 Data Binding 的学习!

三、 初始 Data Binding 
上面的代码算是带领我们进入了 Data Binding 的世界,那我们先从布局文件开始入手 Data Binding 吧。再来看看上面的布局文件。

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <variable  
            name="stu"  
            type="org.loader.androiddatabinding.Student" />  
    </data>  
    ...  
</layout>

我们的根节点变成了layout,在layout的子节点中分成两部分,第一部分是data节点,第二部分才是我们之前的根节点,在data节点下我们又定义了一个variable, 
从名称上看,这应该是一个变量,变量的名称是stu,类型是org.loader.androiddatabinding.Student,这类似我们在 java 文件中这么定义:

org.loader.androiddatabinding.Student stu;

ok,这样很好理解了吧,不过这里要写Student完整的包名,一个还好,如果这里我们需要多个Student呢?要累死? NO,NO,NO, 我们还可以向写 java 文件那样导入包。

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <import type="org.loader.app2.Student" />  
        <variable  
            name="stu"  
            type="Student" />  
    </data>  
    ...  
</layout>

这样写,就类似于 java 的

import org.loader.app2.Student;...Student stu;...

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

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
  ...  
  <LinearLayout  
      android:orientation="vertical"  
      android:layout_width="match_parent"  
      android:layout_height="wrap_content">  
      <TextView  
          android:layout_width="wrap_content"  
          android:layout_height="wrap_content"  
          android:text="@{stu.name}"/>  
  
      <TextView  
          android:layout_width="wrap_content"  
          android:layout_height="wrap_content"  
          android:text="@{stu.addr}"/>  
  </LinearLayout>  
</layout>

恩,注意看两个TextViewandroid:text,它的值是一个以@开始,以 {} 包裹的形式出现,而内容呢?是stu.name。stu 就是我们上面定义的variable
name 还记得吗?是我们Student类中的一个变量。其实这里就会去调用stu.getName()方法。 
好了,很快,我们就入门了 Data Binding,下面让我们来多定义几个变量试试看。

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <import type="org.loader.app2.Student" />  
        <variable  
            name="stu"  
            type="Student" />  
        <variable  
            name="str"  
            type="String"/>  
        <variable  
            name="error"  
            type="boolean"/>  
        <variable  
            name="num"  
            type="int" />  
    </data>  
  
    <LinearLayout  
        android:orientation="vertical"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content">  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{stu.name}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{str}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{String.valueOf(num)}"/>  
    </LinearLayout>  
</layout>

来看看定义的变量,多了好几个,有一个String类型的变量我们并没有导包,这里说明一下,和在 java 里一样,java.lang包里的类,我们是可以不用导包的,再往下,一个booleanint类型的变量,都是 java 基本类型的,所以说嘛,在这里定义变量,你就想成是在 java 里定义就 ok。 
再来看看这几个TextView,第二个,我们直接使用@{str}来为android:text设置成上面定义个str的值,继续往下要注意了,我们使用了

android:text="@{String.valueOf(num)}"

来设置了一个 int 类型的变量,大家都知道我们在给 android:text 设置 int 类型的值时一定要转化为 String 类型,要不它就认为是资源文件了,这里我们还学到了一点,在 xml 中,我们不仅可以使用变量,而且还可以调用方法!

四、 变量定义的高级部分 
在上面,我们学会了如何去在 xml 中定义变量,但是不知道你发现没?我们没有定义像 ListMap 等这样的集合变量。那到底能不能定义呢?答案肯定是可以的,而且定义的方式和我们上面的基本一致,区别就在于我们还需要为它定义 key 的变量,例如:

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data>  
        <import type="org.loader.app2.Student" />  
        <import type="android.graphics.Bitmap" />  
        <import type="java.util.ArrayList" />  
        <import type="java.util.HashMap" />  
        <variable  
            name="stu"  
            type="Student" />  
        <variable  
            name="str"  
            type="String"/>  
        <variable  
            name="error"  
            type="boolean"/>  
        <variable  
            name="num"  
            type="int" />  
        <variable  
            name="list"  
            type="ArrayList<String>" />  
        <variable  
            name="map"  
            type="HashMap<String, String>" />  
        <variable  
            name="array"  
            type="String[]" />  
  
        <variable  
            name="listKey"  
            type="int" />  
        <variable  
            name="mapKey"  
            type="String" />  
        <variable  
            name="arrayKey"  
            type="int" />  
    </data>  
  
    <LinearLayout  
        android:orientation="vertical"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content">  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{stu.name}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{str}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{String.valueOf(num)}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{list[listKey]}"/>  
  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{map[`name`]}"/>  
        <TextView  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:text="@{array[0]}"/>  
    </LinearLayout>  
</layout>

这段代码比较长,但是我们仅关心那几个集合和数组,可以看到我们定义集合和定义普通变量一样,只不过这里我们还指定了一些的泛型,例如:ArrayList&lt;String>。 
下面我们还为下面使用这些集合准备了几个 key,也都是变量。 
继续看看怎么使用,和我们在 java 中使用不同,这里都是以:集合变量名 [key] 的形式使用,如果你的 key 是一个字面字符串可以使用反引号,也可以使用转义后的双引号。恩,这里也没有什么可以说的了,大家多看几遍就掌握了,都是概念性的东西,记住就 ok。

五、在 java 代码中使用 
前面定义了这么多变量,但是我们还没有给他们赋值!在哪赋值呢?肯定是在 java 代码中使用了,大部分情况我们还是在 Activity 中去使用它,以前我们都是在 onCreate 方法中通过 setContentView 去设置布局,但现在不一样了,现在我们是用过 DataBindingUtil 类的一个静态方法 setContentView 设置布局,同时该方法会返回一个对象,什么对象?这个对象有点特殊,它是一个自动生成的类的对象,看下面:

@Override  
   protected void onCreate(Bundle savedInstanceState) {  
       super.onCreate(savedInstanceState);  
       ActivityMainBinding binding = DataBindingUtil.setContentView(this,  
               R.layout.activity_main);  
    }

看到ActivityMainBinding了吗?就是它!那自动生成有什么规则了没?当然有了,记好了:

将我们布局文件的首字母大写,并且去掉下划线,将下划线后面的字母大写,加上 Binding 组成。

看看上面的类,是不是符合这个规则。继续看看这个对象哪来的,是通过

DataBindingUtil.setContentView(thisR.layout.activity_main);

返回的,DataBindingUtil.setContentView 的两个参数分别是当前Activity和布局文件。那接下来,就是我们关心的给变量赋值了。

@Override  
protected void onCreate(Bundle savedInstanceState) {  
   ...  
    binding.setStu(new Student("loader"));  
    binding.setStr("string");  
    binding.setError(false);  
  
    ArrayList<String> list = new ArrayList<String>() {  
        {  
            add("arraylist");  
        }  
    };  
    binding.setList(list);  
    binding.setListKey(0);  
  
    HashMap<StringString> map = new HashMap<StringString>() {  
        {  
            put("name""hashmap");  
        }  
    };  
    binding.setMap(map);  
//        binding.setMapKey("name");  
  
    String[] array = new String[1];  
    array[0] = "array";  
    binding.setArray(array);  
    binding.setArrayKey(0);  
}

一连串的 binding.setXXX,这个 XXX 是什么呢?就是我们在 xml 中定义的那些变量首字母大写了!也没好好说的吧,多看几遍。

Android官方数据绑定框架DataBinding(三)

Android官方数据绑定框架DataBinding(三)

十一、 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对象,并给它提供了GetterSetter方法, 这个ViewDataBinding是干嘛的?我们稍后去讲。继续看看onCreateViewHolder,在这里面,我们首先调用DataBindingUtil.inflate方法返回了一个ViewDataBinding的对象,这个ViewDataBinding是个啥?我们以前没见过啊,这里告诉大家我们之前返回的那些都是ViewDataBinding的子类!继续看代码,我们new了一个holder,参数是肯定是我们的item布局了,继续看,接着我们又把binding设置给了holder,最后返回holder。这时候,我们的holder里就保存了刚刚返回的ViewDataBinding对象,干嘛用呢?继续看onBindViewHolder就知道了。

@Override  
public void onBindViewHolder(ViewHolder viewHolder, int i) {  
    viewHolder.getBinding().setVariable(org.loader.app6.BR.stu, mData.get(i));  
    viewHolder.getBinding().executePendingBindings();  
}

只有两行代码,但是都是我们没有见过的,首先第一行,我们以前都是使用类似binding.setStu这样方法去设置变量,那这个setVariable呢? 为什么没有setStu,这里要记住,ViewDataBinding是我们之前用的那些binding的父类,只有自动生成的那些子类才会有setXXX方法,那现在我们需要在ViewDataBinding中设置变量咋办?这个类为我们提供了setVariable去设置变量,第一个参数是我们的变量名的引用,第二个是我们要设置的值。第二行代码,executePendingBindings的作用是干嘛的?官方的回答是:
当数据改变时,binding会在下一帧去改变数据,如果我们需要立即改变,就去调用executePendingBindings方法。

所以这里的作用就是去让数据的改变立即执行。 
ok,现在看起来,我们的代码更加简洁了,而且不需要保存控件的实例,是不是很爽? 来看看效果:

十二、 View with ID 
在使用Data Binding的过程中,我们发现并没有保存View的实例,但是现在我们有需求需要这个View的实例咋办?难道走老路findViewById?当然不是啦,当我们需要某个view的实例时,我们只要给该view一个id,然后Data Binding框架就会给我们自动生成该view的实例,放哪了?当然是ViewDataBinding里面。 
上代码:

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data >  
        <variable  
            name="str"  
            type="android.databinding.ObservableField<String>" />  
        <variable  
            name="handler"  
            type="org.loader.app7.MainActivity" />  
    </data>  
  
    <TextView  
        android:id="@+id/textView"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="@{str.get}"  
        android:onClick="@{handler.click}"/>  
</layout>

xml中代码没有什么好说的,都是之前的代码,如果在这有点迷糊,建议你还是回头看看上篇博客。需要注意的是, 
我们给TextView了一个id-textView。 
activity,

public class MainActivity extends AppCompatActivity {  
  
    private org.loader.app7.Custom mBinding;  
    private ObservableField<String> mString;  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        mBinding = DataBindingUtil.setContentView(this,  
                R.layout.activity_main);  
        mString = new ObservableField<String>();  
        mString.set("loader");  
        mBinding.setStr(mString);  
        mBinding.setHandler(this);  
    }  
  
    public void click(View view) {  
        mString.set("qibin");  
        mBinding.textView.setTextColor(Color.GREEN);  
    }  
}

通过ViewDataBinding类的实例直接去获取的。

只要我们给了view一个id,那么框架就会在ViewDataBinding中自动帮我们保存这个view的实例,变量名就是我们设置的id。

十三、 自定义setter 
想想这样的一种情景,一个ImageView需要通过网络去加载图片,那我们怎么办?看似好像使用DataBinding不行,恩,我们上面所学到东西确实不能够解决这个问题,但是DataBinding框架给我们提供了很好的扩展,允许我们自定义setter,那该怎么做呢?这里就要引出另一个知识点——BindingAdapter,这是一个注解,参数是一个数组,数组中存放的是我们自定义的’属性’。接下来就以一个例子学习一下BindingAdapter的使用。

<layout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:app="http://schemas.android.com/apk/res-auto">  
    <data >  
        <variable  
            name="imageUrl"  
            type="String" />  
    </data>  
  
    <ImageView  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        app:image="@{imageUrl}"/>  
</layout>

这里我们增加了一个命名空间app,并且注意ImageView的app:image属性,这里和我们自定义view时自定义的属性一样,但是这里并不需要我们去重写ImageView,这条属性的值是我们上面定义的String类型的imageUrl,从名称中看到这里我们可能会塞给他一个url。 
activity,

public class MainActivity extends AppCompatActivity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        org.loader.app8.Custom binding = DataBindingUtil.setContentView(this,  
                R.layout.activity_main);  
        binding.setImageUrl("http://images.csdn.net/20150810/Blog-Image%E5%89%AF%E6%9C%AC.jpg");  
    }  
}

果然在这里我们set了一个url,那图片怎么加载呢?这里就要使用到我们刚才说的BindingAdapter注解了。

public class Utils {  
    @BindingAdapter({"bind:image"})  
    public static void imageLoader(ImageView imageView, String url) {  
        ImageLoaderUtils.getInstance().displayImage(url, imageView);  
    }  
}

我们定义了一个Utils类,这个类你可以随便起名,该类中只有一个静态的方法imageLoader,该方法有两个参数,一个是需要设置数据的view, 
一个是我们需要的url。值得注意的是那个BindingAdapter注解,看看他的参数,是一个数组,内容只有一个bind:image,仅仅几行代码,我们不需要 
手工调用Utils.imageLoader,也不需要知道imageLoader方法定义到哪了,一个网络图片加载就搞定了,是不是很神奇,这里面起关键作用的就是BindingAdapter 
注解了,来看看它的参数怎么定义的吧,难道是乱写?当然不是,这里要遵循一定的规则,

以bind:开头,接着书写你在控件中使用的自定义属性名称。

这里就是image了,不信来看。

<ImageView  
    android:layout_width="match_parent"  
    android:layout_height="wrap_content"  
    app:image="@{imageUrl}"/>

看看运行结果:

十四、 Converters 
Converter是什么呢?举个例子吧:假如你的控件需要一个格式化好的时间,但是你只有一个Date类型额变量咋办?肯定有人会说这个简单,转化完成后在设置,恩,这也是一种办法,但是DataBinding还给我们提供了另外一种方式,虽然原理一样,但是这种方式使用的场景更多,那就是——Converter。和上面的BindingAdapter使用方法一样,这也是一个注解。下面还是以一段代码的形式进行学习。

<layout xmlns:android="http://schemas.android.com/apk/res/android">  
    <data >  
        <variable  
            name="time"  
            type="java.util.Date" />  
    </data>  
  
    <TextView  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:text="@{time}"/>  
</layout>

看TextView的text属性,我们需要一个String类型的值,但是这里确给了一个Date类型的,这就需要我们去定义Converter去转换它, 
activity,

public class MainActivity extends AppCompatActivity {  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        org.loader.app9.Custom binding = DataBindingUtil.setContentView(this,  
                R.layout.activity_main);  
        binding.setTime(new Date());  
    }  
}

去给这个Date类型的变量设置值。怎么去定义Converter呢? 看代码:

public class Utils {  
  
    @BindingConversion  
    public static String convertDate(Date date) {  
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");  
        return sdf.format(date);  
    }  
}

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

好了,到这里DataBinding的知识我们就算学习完了,在学完之后发现这东西也没什么难度,学会使用就ok了,而且android官网也有非常详细的文档

我们今天的关于数据绑定三为Binding指定绑定源的几种方法的分享已经告一段落,感谢您的关注,如果您想了解更多关于Android Data Binding(数据绑定)用户指南、Android Jetpack中DataBinding数据绑定布局和绑定表达式(三)、Android 官方数据绑定框架 DataBinding(一)、Android官方数据绑定框架DataBinding(三)的相关信息,请在本站查询。

本文标签: