Android Architecture Components(四)DataBinding

DataBinding简介

官方文档

The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
DataBinding允许你在layout当中将UI和数据源进行绑定,而不用编程的方式绑定。

Binding components in the layout file lets you remove many UI framework calls in your activities, making them simpler and easier to maintain. This can also improve your app’s performance and help prevent memory leaks and null pointer exceptions.
在layout布局中绑定组件可以让你在activity中移除很多UI操作,使维护更加简单容易。这也可以提高app的性能,防止内存泄漏和空指针异常。


编译环境

要将应用程序配置为使用数据绑定,请将dataBinding元素添加到app模块中的build.gradle文件中,如以下示例所示:

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

Note: You must configure data binding for app modules that depend on libraries that use data binding, even if the app module doesn’t directly use data binding.
注意:您必须为依赖于使用数据绑定的库的应用程序模块配置数据绑定,即使应用程序模块不直接使用数据绑定也是如此

Android Studio对data binding的支持

Android Studio支持许多用于databinding代码的编辑功能。 例如,它支持数据绑定表达式的以下功能:

语法highlight显示
标记表达式语言语法错误
XML代码补全
参考信息,包括导航(例如导航到声明)和快速文档

警告:数组和泛型类型(如Observable类)可能会错误地显示错误。

用于绑定类的新数据绑定编译器

Android Gradle插件版本3.1.0-alpha06包含一个生成绑定类的新数据绑定编译器。 新编译器会逐步创建绑定类,这在大多数情况下会加快构建过程。 要了解有关绑定类的更多信息,请参阅生成的绑定类

布局和绑定表达式

表达式语言允许您编写处理视图调度的事件的表达式。数据绑定库自动生成将布局中的视图与数据对象绑定所需的类。

数据绑定布局文件略有不同,以布局的根标签开头,后跟数据元素和视图根元素。此视图元素是您的根在非绑定布局文件中的位置。

以下代码显示了一个示例布局文件:

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

Note: Layout expressions should be kept small and simple, as they can’t be unit tested and have limited IDE support. In order to simplify layout expressions, you can use custom binding adapters.
使用Bindung Adapters可以自定义绑定。

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

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(),如果该方法存在。

Binding data

默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。 activity_main.xml相应的生成类是ActivityMainBinding。 此类包含布局属性(例如,用户变量)到布局视图的所有绑定,并知道如何为绑定表达式分配值。创建绑定的推荐方法是在扩展布局时执行此操作,如下例所示:

@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);
   
or:

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

我截取了一段我的工程中生成的DataBinding代码:

public abstract class MainTagItemBinding extends ViewDataBinding {

  ...

  @NonNull
  public static MainTagItemBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot) {
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  @NonNull
  public static MainTagItemBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot, @Nullable DataBindingComponent component) {
    return DataBindingUtil.<MainTagItemBinding>inflate(inflater, com.pegg.video.R.layout.main_tag_item, root, attachToRoot, component);
  }
  
  ...
}
Null coalescing operator

如果前操作数不为空,则空合并运算符(??)选择左操作数;如果前操作数为空,则选择右操作数。

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

等价于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
事件处理

数据绑定允许您编写从视图调度的表达式处理事件(例如,onClick()方法)。事件属性名称由侦听器方法的名称确定,但有一些例外。 例如,View.OnClickListener有一个方法onClick(),因此该事件的属性是android:onClick。

click事件有一些专门的事件处理程序需要除android:onClick以外的属性以避免冲突。 您可以使用以下属性来避免这些类型的冲突:

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

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

Method references: In your expressions, you can reference methods that conform to the signature of the listener method. When an expression evaluates to a method reference, Data binding wraps the method reference and owner object in a listener, and sets that listener on the target view. If the expression evaluates to null, Data binding doesn’t create a listener and sets a null listener instead.
在表达式中,您可以引用符合侦听器方法签名的方法。 当表达式求值为方法引用时,Data绑定将方法引用和所有者对象包装在侦听器中,并在目标视图上设置该侦听器。 如果表达式求值为null,则数据绑定不会创建侦听器并改为设置空侦听器。

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

Listener bindings: These are lambda expressions that are evaluated when the event happens. Data binding always creates a listener, which it sets on the view. When the event is dispatched, the listener evaluates the lambda expression.
这些是在事件发生时计算的lambda表达式。 数据绑定总是创建一个侦听器,它在视图上设置。 调度事件时,侦听器将计算lambda表达式。

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

Method references和Listener bindings定之间的主要区别在于实际的侦听器实现是在绑定数据时创建的,而不是在触发事件时创建的。 如果您希望在事件发生时评估lambda表达式,则应使用Listener bindings。

Imports, variables, and includes

The Data Binding Library provides features such as imports, variables, and includes. Imports make easy to reference classes inside your layout files. Variables allow you to describe a property that can be used in binding expressions. Includes let you reuse complex layouts across your app.
imports使布局文件中的类很容易引用。Variables允许您描述可用于绑定表达式的属性。Includes让您在整个应用中重复使用复杂的布局。

A special variable named context is generated for use in binding expressions as needed. The value for context is the Context object from the root View’s getContext() method. The context variable is overridden by an explicit variable declaration with that name.
context的值是来自根View的getContext()方法的Context对象。 使用该名称的显式变量声明覆盖上下文变量。

Data binding doesn’t support include as a direct child of a merge element. For example, the following layout isn’t supported:
数据绑定不支持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>

Work with observable data objects

Any plain-old object can be used for data binding, but modifying the object doesn’t automatically cause the UI to update. Data binding can be used to give your data objects the ability to notify other objects, known as listeners, when its data changes. There are three different types of observable classes: objects, fields, and collections.
任何普通对象都可用于数据绑定,但修改对象不会自动导致UI更新。 数据绑定可用于为数据对象提供在数据更改时通知其他对象(称为侦听器)的能力。 有三种不同类型的可观察类:对象,字段和集合。

When one of these observable data objects is bound to the UI and a property of the data object changes, the UI is updated automatically.
当其中一个可观察数据对象绑定到UI并且数据对象的属性发生更改时,UI将自动更新。

The Observable interface has a mechanism to add and remove listeners, but you must decide when notifications are sent. To make development easier, the Data Binding Library provides the BaseObservable class, which implements the listener registration mechanism. The data class that implements BaseObservable is responsible for notifying when the properties change. This is done by assigning a Bindable annotation to the getter and calling the notifyPropertyChanged() method in the setter, as shown in the following example:
Observable接口有一个添加和删除侦听器的机制,但您必须决定何时发送通知。 为了简化开发,数据绑定库提供了BaseObservable类,该类实现了侦听器注册机制。 实现BaseObservable的数据类负责通知属性何时更改。 这是通过将一个Bindable注释分配给getter并在setter中调用notifyPropertyChanged()方法来完成的,如以下示例所示:

private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

数据绑定在模块包中生成一个名为BR的类,该类包含用于数据绑定的资源的ID。 Bindable注释在编译期间在BR类文件中生成一个条目。 如果无法更改数据类的基类,则可以使用PropertyChangeRegistry对象实现Observable接口,以有效地注册和通知侦听器。


生成的绑定类

数据绑定库生成用于访问布局的变量和视图的绑定类。 此页面显示如何创建和自定义生成的绑定类。

生成的绑定类将布局变量与布局中的视图链接起来。 绑定类的名称和包可以自定义。 所有生成的绑定类都继承自ViewDataBinding类。

默认情况下,类的名称基于布局文件的名称,将其转换为Pascal大小写并向其添加Binding后缀。 上面的布局文件名是activity_main.xml,因此相应的生成类是ActivityMainBinding。 此类包含布局属性(例如,用户变量)到布局视图的所有绑定,并知道如何为绑定表达式指定值。

Views with IDs

The Data Binding Library creates a immutable field in the binding class for each view that has an ID in the layout. For example, the Data Binding Library creates the firstName and lastName fields of type TextView from the following layout:
数据绑定库在绑定类中为每个在布局中具有ID的视图创建不可变字段。 例如,数据绑定库从以下布局创建TextView类型的firstName和lastName字段:

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

The library extracts the views including the IDs from the view hierarchy in a single pass. This mechanism can be faster than calling the findViewById() method for every view in the layout.
该库在一次传递中从视图层次结构中提取包括ID的视图。 此机制比为布局中的每个视图调用findViewById()方法更快。

IDs aren’t as necessary as they are without data binding, but there are still some instances where access to views is still necessary from code.
加载没有数据绑定的控件的ID没有必要,但仍有一些情况需要从代码访问视图。

ViewStubs

与普通视图不同,ViewStub对象从一个不可见的视图开始。 当它们被显示或被明确告知要inflate时,它们会通过inflate另一个布局来替换自己的布局。

由于ViewStub基本上从视图层次结构中消失,因此绑定对象中的视图也必须消失以允许通过垃圾回收声明。 因为视图是final的,所以ViewStubProxy对象取代了生成的绑定类中的ViewStub,使您可以在ViewStub存在时访问它,并在ViewStub inflate时访问inflate的视图层次结构。

在inflate另一个布局时,必须为新布局建立绑定。 因此,ViewStubProxy必须侦听ViewStub OnInflateListener并在需要时建立绑定。 由于在给定时间只能存在一个侦听器,因此ViewStubProxy允许您设置OnInflateListener,它在建立绑定后调用它。

动态变量

有时,特定的绑定类是未知的。 例如,针对任意布局操作的RecyclerView.Adapter不知道特定的绑定类。 它仍然必须在调用onBindViewHolder()方法期间分配绑定值。

在以下示例中,RecyclerView绑定的所有布局都具有项变量。 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();
}
Background Thread

You can change your data model in a background thread as long as it isn’t a collection. Data binding localizes each variable / field during evaluation to avoid any concurrency issues.
您可以在后台线程中更改数据模型,只要它不是集合即可。 数据绑定在evaluation期间本地化每个变量/字段以避免任何并发问题。

Custom binding class names

By default, a binding class is generated based on the name of the layout file, starting with an uppercase letter, removing underscores ( _ ), capitalizing the following letter, and suffixing the word Binding. The class is placed in a databinding package under the module package. For example, the layout file contact_item.xml generates the ContactItemBinding class. If the module package is com.example.my.app, then the binding class is placed in the com.example.my.app.databinding package.
默认情况下,将根据布局文件的名称生成绑定类,以大写字母开头,删除下划线(_),大写以下字母,并为单词Binding添加后缀。 该类放在模块包下的数据绑定包中。 例如,布局文件contact_item.xml生成ContactItemBinding类。 如果模块包是com.example.my.app,则绑定类放在com.example.my.app.databinding包中。

Binding classes may be renamed or placed in different packages by adjusting the class attribute of the data element. For example, the following layout generates the ContactItem binding class in the databinding package in the current module:
通过调整数据元素的class属性,可以重命名绑定类或将绑定类放在不同的包中。 例如,以下布局在当前模块的数据绑定包中生成ContactItem绑定类:

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

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

Binding adapters

The Data Binding Library allows you to specify the method called to set a value, provide your own binding logic, and specify the type of the returned object by using adapters.
数据绑定库允许您指定调用的方法来设置值,提供自己的绑定逻辑,并使用适配器指定返回对象的类型。

Specify a custom method name

Some attributes have setters that don’t match by name. In these situations, an attribute may be associated with the setter using the BindingMethods annotation. The annotation is used with a class and can contain multiple BindingMethod annotations, one for each renamed method. Binding methods are annotations that can be added to any class in your app. In the following example, the android:tint attribute is associated with the setImageTintList(ColorStateList) method, not with the setTint() method:
某些属性具有名称不匹配的setter。 在这些情况下,可以使用BindingMethods注释将属性与setter相关联。 注释与类一起使用,可以包含多个BindingMethod注释,每个注释方法一个注释。 绑定方法是可以添加到应用程序中任何类的注释。 在以下示例中,android:tint属性与setImageTintList(ColorStateList)方法关联,而不是与setTint()方法关联:

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})
提供自定义逻辑

某些属性需要自定义绑定逻辑。 例如,android:paddingLeft属性没有关联的setter。 相反,提供了setPadding(左,上,右,下)方法。 使用BindingAdapter注释的静态绑定适配器方法允许您自定义如何调用属性的setter。

Android框架类的属性已经创建了BindingAdapter注释。 例如,以下示例显示了paddingLeft属性的绑定适配器:

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

参数类型很重要。 第一个参数确定与属性关联的视图的类型。 第二个参数确定给定属性的绑定表达式中接受的类型。

绑定适配器可用于其他类型的自定义。 例如,可以从工作线程调用自定义加载程序来加载图像。

当发生冲突时,您定义的绑定适配器将覆盖Android框架提供的默认适配器。

您还可以使用接收多个属性的适配器,如以下示例所示:

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.get().load(url).error(error).into(view);
}

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

注意:数据绑定库会忽略自定义命名空间以进行匹配。

如果imageUrl和error都用于ImageView对象并且imageUrl是字符串且error是Drawable,则调用适配器。 如果希望在设置任何属性时调用适配器,则可以将适配器的可选requireAll标志设置为false,如以下示例所示:

@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
  if (url == null) {
    imageView.setImageDrawable(placeholder);
  } else {
    MyImageLoader.loadInto(imageView, url, placeholder);
  }
}

注意:发生冲突时,绑定适配器会覆盖默认数据绑定适配器。

绑定适配器方法可以选择在其处理程序中使用旧值。 采用旧值和新值的方法应首先声明属性的所有旧值,然后是新值,如下例所示:

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

Use ViewModel to manage UI-related data

数据绑定库与ViewModel组件无缝协作,ViewModel组件公开布局观察到的数据并对其更改做出反应。 将ViewModel组件与数据绑定库一起使用,可以将UI逻辑从布局移动到组件中,这些组件更易于测试。 数据绑定库可确保在需要时绑定和取消绑定数据源。 剩下的大部分工作都在于确保您公开正确的数据。

要将ViewModel组件与数据绑定库一起使用,必须实例化从Viewmodel类继承的组件,获取绑定类的实例,并将ViewModel组件分配给绑定类中的属性。 以下示例显示如何将该组件与库一起使用:

class ViewModelActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Obtain the ViewModel component.
        UserModel userModel = ViewModelProviders.of(getActivity()).get(UserModel.class);

        // Inflate view and obtain an instance of the binding class.
        UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);

        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel;
    }
}

<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />
使用Observable ViewModel可以更好地控制绑定适配器

您可以使用实现Observable的ViewModel组件来通知其他应用程序组件有关数据更改的信息,类似于使用LiveData对象的方式。

在某些情况下,您可能更喜欢使用ViewModel组件来实现Observable接口,而不是使用LiveData对象,即使您丢失了LiveData的生命周期管理功能。使用实现Observable的ViewModel组件可以更好地控制应用程序中的绑定适配器。例如,此模式使您可以在数据更改时更好地控制通知,还允许您指定自定义方法以在双向数据绑定中设置属性的值。

要实现可观察的ViewModel组件,必须创建一个继承自ViewModel类并实现Observable接口的类。当观察者使用addOnPropertyChangedCallback()和removeOnPropertyChangedCallback()方法订阅或取消订阅通知时,您可以提供自定义逻辑。您还可以提供在notifyPropertyChanged()方法中属性更改时运行的自定义逻辑。以下代码示例演示如何实现可观察的ViewModel:

/**
 * A ViewModel that is also an Observable,
 * to be used with the Data Binding Library.
 */
class ObservableViewModel extends ViewModel implements Observable {
    private PropertyChangeRegistry callbacks = new PropertyChangeRegistry();

    @Override
    protected void addOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }

    @Override
    protected void removeOnPropertyChangedCallback(
            Observable.OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }

    /**
     * Notifies observers that all properties of this instance have changed.
     */
    void notifyChange() {
        callbacks.notifyCallbacks(this, 0, null);
    }

    /**
     * Notifies observers that a specific property has changed. The getter for the
     * property that changes should be marked with the @Bindable annotation to
     * generate a field in the BR class to be used as the fieldId parameter.
     *
     * @param fieldId The generated BR id for the Bindable field.
     */
    void notifyPropertyChanged(int fieldId) {
        callbacks.notifyCallbacks(this, fieldId, null);
    }
}

双向数据绑定

使用单向数据绑定,您可以在属性上设置值,并设置对该属性中的更改作出反应的侦听器

单向绑定:
<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@{viewmodel.rememberMe}"
    android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>

双向绑定:
<CheckBox
    android:id="@+id/rememberMeCheckBox"
    android:checked="@={viewmodel.rememberMe}"
/>

@ = {}表示法(主要包括“=”符号)接收属性的数据更改并同时侦听用户更新。

为了对支持数据的更改做出反应,您可以使布局变量成为Observable的实现,通常是BaseObservable,并使用@Bindable注释,如以下代码片段所示:

public class LoginViewModel extends BaseObservable {
    // private Model data = ...

    @Bindable
    public Boolean getRememberMe() {
        return data.rememberMe;
    }

    public void setRememberMe(Boolean value) {
        // Avoids infinite loops.
        if (data.rememberMe != value) {
            data.rememberMe = value;

            // React to the change.
            saveData();

            // Notify observers of a new value.
            notifyPropertyChanged(BR.remember_me);
        }
    }
}
使用自定义属性进行双向数据绑定

对自定义属性使用双向数据绑定,需要使用@InverseBindingAdapter和@InverseBindingMethod注释。

例如,如果要在名为MyView的自定义视图中的“time”属性上启用双向数据绑定,请完成以下步骤:

1.注释设置初始值的方法,并在使用@BindingAdapter更改值时进行更新:

@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue;
    }
}

2.使用@InverseBindingAdapter注释从视图中读取值的方法:

@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
    return view.getTime();
}

此时,数据绑定知道在数据更改时要做什么(它调用使用@BindingAdapter注释的方法)以及在view属性更改时调用的内容(它调用InverseBindingListener)。但是,它不知道属性何时或如何更改。

为此,您需要在视图上设置一个侦听器。 它可以是与自定义视图关联的自定义侦听器,也可以是通用事件,例如失去焦点或文本更改。 将@BindingAdapter注释添加到为该属性的更改设置侦听器的方法:

@BindingAdapter("app:timeAttrChanged")
public static void setListeners(
        MyView view, final InverseBindingListener attrChange) {
    // Set a listener for click, focus, touch, etc.
}
Converters

如果绑定到View对象的变量需要在显示之前以某种方式进行格式化,翻译或更改,则可以使用Converter对象。

例如,使用显示日期的EditText对象:

<EditText
    android:id="@+id/birth_date"
    android:text="@={Converter.dateToString(viewmodel.birthDate)}"
/>

viewmodel.birthDate属性包含Long类型的值,因此需要使用转换器对其进行格式化。

因为正在使用双向表达式,所以还需要一个逆转换器让库知道如何将用户提供的字符串转换回后备数据类型,在本例中为Long。 此过程通过将@InverseMethod注释添加到其中一个转换器并使此注释引用逆转换器来完成。 以下代码段中显示了此配置的示例:

public class Converter {
    @InverseMethod("stringToDate")
    public static String dateToString(EditText view, long oldValue,
            long value) {
        // Converts long to String.
    }

    public static long stringToDate(EditText view, String oldValue,
            String value) {
        // Converts String to long.
    }
}
双向属性

当您使用下表中的属性时,该平台为双向数据绑定提供内置支持。

ClassAttribute(s)Binding adapter
AdapterViewandroid:selectedItemPosition android:selectionAdapterViewBindingAdapter
CalendarViewandroid:dateCalendarViewBindingAdapter
CompoundButtonandroid:checkedCompoundButtonBindingAdapter
DatePickerandroid:year android:month android:dayDatePickerBindingAdapter
NumberPickerandroid:valueNumberPickerBindingAdapter
RadioButtonandroid:checkedButtonRadioGroupBindingAdapter
RatingBarandroid:ratingRatingBarBindingAdapter
SeekBarandroid:progressSeekBarBindingAdapter
TabHostandroid:currentTabTabHostBindingAdapter
TextViewandroid:textTextViewBindingAdapter
TimePickerandroid:hour android:minuteTimePickerBindingAdapter

源码分析

本来计划写这部分,但是在网上看到了
DataBinding学习笔记(一)源码分析 author:listen2code 这篇文章,写的很详细,而且读起来特别易懂

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值