Data Binding官方用户指南

Data Binding Guide

Data Binding即数据绑定,Data Binding 库实现在布局文件中实现数据绑定申明,使数据的变化引起视图的自动更新,减少了逻辑代码,在Android中可以很方便的实现MVVM的开发模式。

最低运行环境: Android 2.1 (API level 7+) 环境要求:Android Plugin for Gradle 1.3.0-beta4或者更高

Beta release

当前是beta版本,可能会有一些bug,也有可能现在不适合在你的项目中使用。Google希望大家使用它,并给出一个好的或者不好的反馈。另外需要注意的是,这个beta版跟后面的正式版使用方式上可能会有比较大的变化,在实际项目中慎用。

配置环境Setting Up Work Environment:

在project最上层的build.gradle android标签下面加入:

   dependencies {
       classpath "com.android.tools.build:gradle:1.3.0-beta4"
       classpath "com.android.databinding:dataBinder:1.0-rc1"
   }

同时确保jcenter在版本仓库列表中

allprojects {
   repositories {
       jcenter()
   }
}

在所有需要使用到Data Binding的module中,都需要在build.gradle的android plugin下面增加:

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

data binding插件的作用是增加必须的编译配置依赖到项目中。

Data Binding布局文件

Data-binding布局文件稍微有些不同,在根标签下面有一个data标签和root 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"></variable>
   </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>
       <textview android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"></textview>
   </linearlayout>
</layout>

user 是会在这个布局中用到的一个变量。

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

在布局中使用的使用要使用“@{}”,如下代码表示:TextView中文本设置为user的firstName

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

数据对象Data Object

假设我们有个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;
   }
}

这个对象的数据是一直不变的。很多应用中一个数据可能变化一次以后就不再变了。有时候我们也使用下面的JavaBean结构。

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

从data binding的角度来看,这两个类是一样的。对于@{user.firstName} ,在给TextView的android:text赋值的时候,系统可以像第一个类中那样直接读取firstName的值,或者像第二个类中调用getFirstName() ,当然,如果firstName() 存在的话,也是会被调用到的。(至于同时存在的时候,调用优先顺序请自己测试。)

绑定数据Binding Data

默认情况下,会有一个基于layout文件名字产生一个Binding类,然后在后面加个Binding. 上面的main_activity.xml 产生的类名字就是MainActivityBinding 这个类持有了layout中所有的绑定属性(比如user变量)。最简单的做法是在view填充的时候创建绑定bindings.

@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 的Adaptper中使用data binding.使用如下代码:

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

绑定事件Binding Events

事件可以简单的绑定到handler方法中,就像android:onClick被分配到Activity的onClick(View v)中一样。

比如我们Handler中有下面两个方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
    public void onClickEnemy(View 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>
       <variable name="user" type="com.example.User"></variable>
   </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>
       <textview android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"></textview>
   </linearlayout>
</layout>

布局相关详细Layout Details

导入Imports

如果在data元素中药使用其他的类,代码和java差不多:

<data>
    <import type="android.view.View"></import>
</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}"></textview>

如果出现类名重复的情况,可以指定alias:

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

下面是变量定义写法:

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

注意:现在 Android Studio还没有在IDE实现layout文件中自动补全import。 类型转换什么的也是阔以的。

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

导入类后,可以使用其中的静态方法:

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

和java一样,java.lang.*下面的包是自动导入的。

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

变量类型是在编译是检查的。如果一个类实现了Observable ,那么变量值变化后UI会跟着变化的。

如果同一个同一布局有多个适配文件,文件中的变量最终会合并到一起,所以这些文件中不能存在同名的变量。

生成的绑定类binding class针对每个变量都有getter/setter方法,再调用setter方法前,这些变量都会有默认值,引用类型为null,int为0…

自定义绑定类名字Custom Binding Class Names

默认情况下,绑定类名字是布局文件名字去掉“_”,首字母大写,最后加上Binding。这个文件会被放在modul包下面的package包中,比如contact_item.xml 生成ContactItemBinding 如果module名字是com.example.my.app 这个ContactItemBinding.java 会被放在 com.example.my.app.databinding下面。

绑定类可以被重命名或者放在其他package下面,调整方式如下:

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

上面的文件还是会生成在databinding 下面。如果想生成在其他package下面,和manifest类似,以”.”开头:

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

当然也可以像下面一样:

<data class="com.example.ContactItem">
    ...
</data>
嵌套Includes

在嵌套的布局中使用绑定类,需要使用bind命名空间xmlns:bind="http://schemas.android.com/apk/res-auto

< ?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"></variable>
   </data>
   <linearlayout android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"></include>
       <include layout="@layout/contact"
           bind:user="@{user}"></include>
   </linearlayout>
</layout>

注意:name.xmlcontact.xml 中必须要有user变量。

下面的 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"></variable>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"></include>
       <include layout="@layout/contact"
           bind:user="@{user}"></include>
   </merge>
</layout>

表达式语法Expression Language

共有特性Common Features

下面的用法和java一致

Mathematical + - / * % String concatenation + Logical && || Binary & | ^ Unary + - ! ~ Shift >> >>> < < Comparison == > <>= < = instanceof Grouping () Literals - character, String, numeric, null Cast Method calls Field access Array access [] Ternary operator ?:

Examples:

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}"
防止NPE Avoiding NullPointerException

Generated data binding code automatically checks for nulls and avoid null pointer exceptions. For example, in the expression @{user.name}, if user is null, user.name will be assigned its default value (null). If you were referencing user.age, where age is an int, then it would default to 0. 系统已经针对null做了处理,如果user为null,@{user.name} 默认为null。user.age(int类型),会默认为0

集合操作Collections

方便起见,如下集合烈性: arrays, lists, sparse lists, and maps, 都可以通过 [] 进行操作.

<data>
    <import type="android.util.SparseArray"></import>
    <import type="java.util.Map"></import>
    <import type="java.util.List"></import>
    <variable name="list" type="List&lt;String>"/>
    </variable><variable name="sparse" type="SparseArray&lt;String>"/>
    </variable><variable name="map" type="Map&lt;String, String>"/>
    </variable><variable name="index" type="int"></variable>
    <variable name="key" type="String"></variable>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
字符串字面值String Literals

为了避免双引号(“)转义的问题,可以配合单引号(‘)一起使用

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

下面的写法也ok

android:text="@{map[`firstName`}"
android:text="@{map[&quot;firstName&quot;]}"
资源文件相关Resources

根据屏幕尺寸加载不同的padding值

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

有些资源需要详细的类型,转换方式如下:

TypeNormal ReferenceExpression Reference
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList

数据对象Data Objects

pojo对象可以通过数据绑定来展示到ui,但是修改pojo对象不会使UI刷新。数据绑定真正强大的地方是数据变化的时候UI可以自动刷新。有以下三种机制可以实现:Observable objects, observable fields, observable collections.

当与ui绑定上面的对象数据发生变化时,UI会自动刷新。

Observable Objects

实现Observable 接口的类可以依附attach一个listner到绑定的对象上面,监听绑定对象数据变化。 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 注解编译时会在modul目录生成BR 类文件。

ObservableFields

创建Observable 可能有点费时间,如果想省时一些,可以考虑ObservableField 或者其子类: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable. 在数据类中创建变量:

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

使用:

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

Observable Collections

对于复杂一些的结构,可以参考下面的写法 如果key是String类型:

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

In the layout, the map may be accessed through the String keys:

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

如果key是integer,建议用ObservableArrayList

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

In the layout, the list may be accessed through the indices:

<data>
    <import type="android.databinding.ObservableList"></import>
    <import type="com.example.my.app.Fields"></import>
    <variable name="user" type="ObservableList&lt;Object>"/>
</variable></data>
…
<textview android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>
<textview android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"></textview>

Binding生成Generated Binding

创建

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

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

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

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

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

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

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

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

它会生成如下的Binding类:

public final TextView firstName;
public final TextView lastName;

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

变量Variables

每个Variable会有访问方法。

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

它会在Binding中生成setters和getters:

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

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

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

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

Binding进阶
动态Variables

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

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

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

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

后台线程

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

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

自动Setters

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

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

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

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

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

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

自定义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适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。

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

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

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

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

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

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

事件处理器只能用在只有一个抽象方法的接口或者抽象类中。比如:

@BindingAdapter("android: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);
        }
    }
}

当一个listener有多个方法的时候,它必须被分隔成多个listener。比如View.OnAttachStateChangeListener 有两个方法:onViewAttachedToWindow()onViewDetachedFromWindow() 。我们必须创建两个带有不同属性和handler处理器的接口

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

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

由于修改一个listener会影响到其他的,我们必须定义三个不懂的binding adpater.一个是所有方法的,两个分开的。

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

转换

对象转换

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

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

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

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

自定义转换

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

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

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

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

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

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值