安卓数据绑定指南

安卓数据绑定指南

本文介绍了如何使用数据绑定库写声明布局和减少绑定应用程序逻辑和布局所需的粘合代码。

数据绑定库提供了灵活性和广泛的兼容性 - 这是一个支持库,让你可以在Android平台版本Android 2.1(API级别7+)以上使用它。

要使用数据绑定,Android的插件要求Gradle 1.5.0-alpha1或更高。

构建环境

要开始使用数据绑定,在Android SDK管理器支持库下载所需要的库。

要配置您的应用程序使用数据绑定,应用程序模块中build.gradle文件里添加数据绑定元素。

使用下面的代码片段来配置数据绑定:

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

数据绑定布局文件

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

数据绑定布局文件略有不同,它是一个视图的根元素。

示例代码如下:

<?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标签中variable描述了一个可能在布局文件中使用的属性

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

布局中的表达式都写在使用“@{}”语法属性的属性。在这里,TextView的文本设置为用户的firstName属性:

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

数据对象

假设现在你有一个纯Java对象User(POJO):

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

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

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

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

绑定数据

默认情况下,绑定类将基于布局文件的名称来产生,并在后面添加“Binding”。例如上述布局文件是main_activity.xml所以产生名是MainActivityBinding。此类包含了所有从布局文件中绑定的属性(例如:user visable),并且知道如何分配表达式绑定的值。创建绑定最简单的方式是通过inflating:

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

你完成了!运行应用程序,你会看在UI中看到测试的User对象。另外,您也可以通过获取视图:

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

如果您正在使用数据绑定一个ListView或RecyclerView适配器内部的item,你可能更愿意使用:

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

绑定事件

事件可以直接被绑定的处理方法上,类似android:onClick属性可以直接被绑定到Activity中的某个方法上。

事件属性名称由除少数的例外监听方法的名称管辖。例如:View.OnLongClickListener有一个方法onLongClick(),所以该事件的属性是onLongClick.

要将事件分配给它的处理程序,使用正常绑定表达式,其值为是调用方法的名称。例如,如果你的数据对象有两种方法:

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

绑定表达式可以指定一个视图点击监听器:

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

一些专门的单击事件处理程序存在,他们需要其它属性除了android:onClick以避免冲突。已经创建了下面的属性,以避免这样的冲突:

Class             Listener Setter                                     Attribute
SearchView      setOnSearchClickListener(View.OnClickListener    android:onSearchClick
ZoomControls    setOnZoomInClickListener(View.OnClickListener)   android:onZoomIn
ZoomControls    setOnZoomOutClickListener(View.OnClickListener)  android:onZoomOut

布局细节

Imports

零个或多个import元素可以在data元素中使用。它允许你的布局文件中更容易引用类,就像在Java中。

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

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

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

当有类名冲突,其中一个类可以被重命名为一个别名“alias”

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

现在,在布局文件中Vista可以被当做com.example.real.estate.View使用,View可以被当做android.view.View使用。

导入types的用法:

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




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

在表达式引用静态字段和方法时,导入的类型,也可以使用:

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

正如在Java中的java.lang.*是自动导入的。

Variables(变量)

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

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

变量的类型是在编译时检查,因此,如果一个变量实现了Observable,或者是Observable Collection,那么在类型中应该被反射。如果这个变量是个没有实现Observable*接口普通的类或者接口,这个变量将不会被发现。

当有不同配置(例如横向或纵向)的不同布局文件,变量将被合并。不能有这些布局文件之间相互冲突的变量定义。

生成绑定类将为每个所描述的变量生成一个setter和getter方法。变量将采取默认的Java值,直到调用setter时 - 引用类型的为null,int为0,boolean为false,等等。

特殊变量context将根据需要在绑定表达式中使用。context的值是一个Context从根视图的geContext()方法中获取。这个context变量将通过与它一样名称的变量重写。

自定义绑定类名

缺省情况下,基于所述布局文件的名称,以大写开始它产生Binding类,除去下划线(_)和利用下列字母,然后后面添加“Binding”。这个类将被放置在一个数据绑定包模块包下。例如,所述布局文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么将被置于com.example.my.app.databinding。

通过调整data元素下的class属性,可以重命名绑定类,并可重置它的包名。例如:

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

生成了项目模块包下绑定包的ContactItem类。如果这个类需要生成在项目模块下的不同包下,可以在前面加前缀“.”

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

在这种情况下,ContactItem是在直接在模块包生成。如果提供完整的包,可以使用任何包:

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

Includes (包括)

变量可以传递到包含相同应用程序命名空间和变量名称的属性的布局文件中使用:

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

这里,必须有一个user变量在name.xml和contact.xml布局文件中。

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

表达式语言

共同特征

  • 数学运算符:+ - * %
  • 字符串连接符:+
  • 逻辑运算符:&& ||
  • 二进制:& | ^
  • 一元运算符:+ - ! ~
  • 位移: >>> >> <<
  • 比较:== > < >= <=
  • instanceof
  • 组:()
  • 文字 - 字符,字符串,数字: null
  • Cast
  • Method calls
  • Field access
  • 数组访问:[]
  • 三元运算符: ? :

例如:

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

Missing Operations(剩余的操作)

  • this
  • super
  • new
  • Explicit generic invocation(明确通过调用)

Null Coalescing Operator(空合并运算符)

空合并运算符(??)如果不为空选择左侧数,如果为空选择右侧数

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

上面代码等同于:

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

Property Reference(属性参考)

在“写下你的第一个数据绑定表达式”描述过了

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

避免空指针异常

生成的数据绑定代码会自动检查空值和避免空指针异常。例如,在表达式@{user.name},如果用户为空,user.name将被分配其缺省值(null)。如果你引用user.age,其中年龄是int,那么它会默认为0。

Collections(集合)

常见的集合: arrays, lists, sparse lists, and maps, 可以使用 [] 操作符方便的访问。

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

String Literals(字符串字面)

当使用属性值围绕单引号,很容易在表达式中使用双引号:

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

另外,也可以用双引号包围的属性值。这样做时,字符串应该要么使用&QUOT;或反引号(’)。

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

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

有些资源需要显式类型的评估。(参见https://developer.android.com/intl/zh-cn/tools/data-binding/guide.html

Data Objects(数据对象)

任何普通的旧Java对象(POJO),可用于数据绑定,但修改POJO不会导致UI更新。数据绑定真正的力量在于当给定的数据对象发生变化时给你通知。有三种不同的通知机制,Observable objects, observable fields, and observable collections.

当其中的一种通知机制绑定数据到UI,当一个数据的对象的属性发生变化。UI将会被自动更新。

Observable Objects

实现Observable接口的类将允许绑定一个侦听器附加到绑定的对象,以侦听对象所有的属性变化。

Observable接口有一个添加和删除监听器的机制,但是通知是由开发人员发出的。为了使开发更容易,创建一个基类,BaseObservable,是为了实现监听器注册机制。数据实施者依然是负责在属性更改通知。这是通过一个Bindable注解到getter和在setter里通知.

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

Bindable注解在编译期间生成到BR类中。BR类将被生成在项目的模块包下。如果用于数据类的基类不能改变,则观测接口可以使用便利PropertyChangeRegistry存储和有效地通知听众来实现。

ObservableFields

做少量的工作就可以实现数据观察类,所以开发者如果想节省时间或者使用少量的属性可以使用ObservableField或者它的兄弟类ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable。

ObservableFields是具有单个字段自包含观察对象。原始版本避免在访问时操作装箱和拆箱。

要使用,在数据类中创建一个public final field 。

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

要访问值,请使用set和get的访问方法。

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

Observable Collections

一些应用程序使用更多的动态结构来保存数据。可观察的集合允许对这些数据对象键控访问。当关键是引用类型,如String ObservableArrayMap是非常有用的。

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

在布局,map集合可通过String键来访问:

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

Generated Binding (生成的绑定)

生成绑定类链接布局内的视图的布局变量的。如前面所讨论的,绑定的名称,包可被定制。生成绑定类都继承ViewDataBinding。

Creating

binding应该在infaltion后创建。有绑定到布局的几种方法。最常见的是用在Binding类的静态方法。inflate方法inflates view的各层次,并一步绑定全部。 还有一个更简单的版本,只需要LayoutInflater和一个采用一个的ViewGroup:

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

如果布局中使用不同的机构inflated ,它可以单独的约束:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时不能预先知道的绑定。在这种情况下,可以使用DataBindingUtil类创建绑定:

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

Views With IDs

在布局文件中一个public final field 将为每个View生成一个ID.这种绑定在View层次结构中单传,通过Id提取View.这种机制在一些Views中比findViewbyId要快。例如:

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

将生成一个绑定类:

public final TextView firstName;
public final TextView lastName;

ID是几乎没有必要的,因为没有数据绑定,但仍有某些情况下访问视图仍然是必要的代码。

Variables(变量)

每个变量都将获得访问方法。

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

将生成绑定getter和setter方法:

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

ViewStubs

ViewStubs与正常的View有点不同,它们开始时是不可见的,需要明确的告诉inflate是visible.它们通过inflating另一个layout来取代自己的layout

因为ViewStub本质View层次结构消失,在绑定对象的视图也必须消失,以便收藏。因为View是final;ViewStubProxy对象取ViewStub的地方,提供开发者访问ViewStub当它的存在,也可访问inflated View 的层次。当ViewSub已经被inflated.

当inflating另一个布局时,一个binding必须确定,因此,ViewStubProxy对象必须监听ViewStub的ViewStub.OnInflateListener方法,并在此时确定binding.因为只有一个可以存在,ViewStubProxy允许开发者在它上设置一个OnInflateListener监听,它将在建立绑定之后调用。

Advanced Binding(高级绑定)

Dynamic Variables (动态变量)

有时,一些特殊的绑定是未知的。例如: RecyclerView.Adapter对任意布局操作,并不知具体的绑定类。它仍然必须在onBindViewHolder(VH, int)调用时绑定具体的值。

在这个例子中,该RecyclerView结合所有布局有一个“item”变量。该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();
}
Immediate Binding(直接绑定)

当一个变量或观察者变化,binding将被调度的下一个帧之前发生变化。很多时候,但是,绑定时必须立即执行。要强制执行,使用executePendingBindings()方法。

Background Thread

您可以在后台线程改变您的数据模型,只要他不是一个集合。数据绑定将本地化每个变量、field;以避免任何并发问题。

Attribute Setters (属性设置)

每当绑定值的变化,生成绑定类必须调用setter方法在绑定表达式关联的View。数据绑定框架有方式来调用定制的方法设置值。

Automatic Setters(自动设置)

对于一个属性,数据绑定会尝试查找setAttribute的方法。该属性的命名空间并不重要,只有属性的本身。

例如:TextView的android:text属性表达式,文本设置是将查找setText(String)方法。如果表达式返回一个int,数据绑定将搜索的setText(int)方法。如果有必要请注意表达式的返回值。注意即使没有给定的属性名称存在,数据绑定也会工作。此后通过数据绑定你可以轻松的“create”属性对于任何一个setter。例如,支持DrawerLayout没有任何属性,但大量的setter。您可以使用autonatic setter使用其中之一。

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

Renamed Setters

一些属性不按名称匹配setter方法。对于这些方法,属性可以与通过BindingMethods注解设定器相关联。这必须与一个类相关联,并且包含BindingMethod注解,一个用于每个重命名方法。例如:android:tint属性实际上是与setImageTintList(ColorStateList)相关联而不是setTint.

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

这是不可能的开发人员将需要重命名setter; Android框架的属性已经实施。

Custom Setters

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

Android的属性已经创造了BindingAdapters。例如,这里是一个paddingLeft:

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

Binding适配器对于其他自定义类型也是有用的。例如:一个自定义的加载器可以在off-thread中调用去加载图片。

当有冲突时开发人员自定义的数据绑定适配器需要重写默认的数据绑定适配器。

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

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


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

如果图片网址和错误都被用于一个ImageView的和图片网址是一个字符串和错误是drawable该适配器将被调用。

  • 自定义命名空间匹配过程中被忽略。
  • 你也可以为android命名空间写个适配器。

新旧值对比,当新值如旧值不相同时才去绑定

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

当一个监听器有多个方法,它将被分成多个监听器。例如, View.OnAttachStateChangeListener 有两个方法: onViewAttachedToWindow() 和 onViewDetachedFromWindow().我们必须创建两个不同的接口来区分属性去处理他们。

 @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

因为改变一个监听器也将影响其他的,我们必须具有三个不同的binding适配器,一个用于每个属性,一个用于两者,它们都应该被设置。

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

上述例子比普通的要复杂一些,因为View使用add和remove添加和移除监听器,而不是使用View.OnAttachStateChangeListener的set方法。这个android.databinding.adapters.ListenerUtil类帮助跟踪先前的监听以便它们可以在Binding Adapter中移除。

Converters (转换器)

Object Conversions(对象转换器)

当一个对象从绑定表达式返回,一个setter方法将会被选中从automatic ,renamed custom setters中。这个对象将会被转换为选中的setter的参数类型。这个对于ObservableMap保持数据是便利的。例如:

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

userMap返回的对象类型将会被转换为与setText(CharSequence)相匹配的类型,当有可能造成参数类型混乱时,开发人员需要在表达式中转换。

Custom Conversions

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

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

这里,背景需要可绘制,但颜色是一个整数。每当一个可绘制预期并返回一个整数,整型应转换为一个ColorDrawable。这种转换是利用具有BindingConversion注释的静态方法完成的:

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

需要注意的是转换只发生在setter的水平,所以它是不允许这样的混合类型:

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

Android Studio Support for Data Binding

Android Studio支持数据绑定表达式的语法高亮和标志在编辑器中的表达式语言语法错误。

因为如果提供的数据绑定表达式预览窗格中显示的默认值。在从布局XML文件中的元素的下面的例子摘录,预览窗格显示TextView的占位符默认文本值。

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

如果您需要在项目的设计阶段就显示一个默认值,你也可以使用工具的属性,而不是默认的表达式的值,如 Designtime Layout Attributes中描述的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值