1. 程式人生 > >Android Data Binding Library 官方文件(譯)

Android Data Binding Library 官方文件(譯)

Data Binding Library (資料繫結庫),旨在減少繫結應用程式邏輯和佈局所需的一些耦合性程式碼
最低支援Android 2.1 (API Level 7)

構建環境

使用gradle外掛1.5-alpha1以上
在build.gradle新增如下宣告

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

注:如果子Moudle使用了資料繫結,那麼最好也在app Moudle中配置一下

資料繫結的佈局檔案

書寫第一組資料繫結表示式

<?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>描述了一個變數user,它對應的型別為”com.example.User”
佈局中使用“@ { }”語法來書寫表示式。如為TextView的文字設定成user. firstName:

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

資料物件

a plain-old Java object (POJO) for User:

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

在資料繫結中,訪問@{user.firstName},那麼預設就會訪問同名的屬性firstName,或對應的getFirstName方法

繫結資料

預設情況下,繫結類將基於佈局檔案的名稱來產生
如佈局檔案為main_activity.xml,這樣生成的類是 MainActivityBinding
如下設定Activity的contentView:

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

上文說,在xml中用表示式可以獲取資料,那麼同樣的,在程式碼中也可以設定資料,如這裡的:binding.setUser(user);
還可以使用inflate的方式來獲取生成資料繫結類:

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

如果是在一個adapter中來使用,那麼可以如下:

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

事件處理

可以在xml中編寫表示式處理事件。比如 View.OnLongClickListener有一個方法為onLongClick(),那麼它對應的事件就是android:onLongClick。當繫結事件後,會自動將該listener繫結在View上。處理事件有兩種方法,
1. 方法引用:可以引用符合listener 任意方法簽名規則的方法。
2. 監聽器繫結:使用lambda表示式

方法引用

比如定義一個符合OnClickListener#onClick(View view)方法引數簽名規則的方法:

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

如下在xml中繫結並使用:

<?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="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

監聽器繫結

表示式使用的方法就可以定義的比較隨意了,不用像”方法引用”那樣遵循特定規則,如一個方法

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

使用它

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

注:只允許由lambda表示式作為根元素的表示式

像上面,我們並沒有定義onClick(android.view.View) 引數view。監聽器繫結,允許我們忽略所有的引數。如果想要使用引數,可以忽略引數型別,而隨意定義一個名字。比如,上面的表示式中寫上view引數:

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

如果要使用該引數,可以在對應回撥方法中也定義上:

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

您可以使用一個lambda表示式,它操作多個引數:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
<CheckBox 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> 
            presenter.completeChanged(task, isChecked)}" />

如果事件有返回值,那麼我們定義的方法,最好也有相應的返回值。如:

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

如果表示式對於null物件無法評估,那最好對應方法返回一個基本資料型別的預設值。 比如 null for reference types, 0 for int, false for boolean.

如下使用一個三元表示式:

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

避免複合監聽

存在一些專門的單擊事件處理程式,他們需要一個屬性(除了android:onClick)以避免衝突:
這裡寫圖片描述

佈局細節

import

在<data>標籤中,import 一些class,就像在java 程式碼中一樣

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

在layout佈局中使用:

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

當有類名稱衝突,其中一個類可以重新命名一個別名:

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

在<variable>標籤來使用import中的型別:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>
<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程式碼中一樣,也會自動引入java.lang.*

Variable

可以定義多個<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>

這些variable變數在編譯時會進行型別檢查,如果一個variable實現了Observable介面或是一個Observable的collection,那就需要在type中宣告;如果不是一個Observable的實現,那麼該variable就不會被觀察到

不同的配置有不同的佈局檔案(如橫屏或豎屏),在這些佈局檔案之間,不能定義相互衝突的變數。

自動生成的繫結類,對每個variable,都會生成 setter和getter方法。在setter方法被呼叫前,它會有一個預設值,比如 空引用型別null、int型別0、boolean型別false

一個特定的變數context在表示式需要時,會被生成。這個context值為根view的getContext()值。

自定義繫結 class names

預設情況下,基於佈局檔案的名稱而生成繫結類,開頭大寫,然後移除下劃線,再加上字尾”Binding”。這個類將被放置在一個數據繫結包中,該包位於moudle包下。例如, 佈局檔案contact_item.xml將生成ContactItemBinding。如果moudle的包名為com.example.my.app, 那麼它將被放置在com.example.my.app.databinding下

通過調整data元素的class屬性,繫結類可以重新命名或放置在不同的包。例如:

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

這會生成在moudle下的databinding包下,如果要生成moudle包下,可以使用”.”字首:

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

在下面這種情況下,ContactItem直接生成在模組包下。可以使用任何完整的包路徑:

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

include

variables可以傳遞給使用了include標籤的子佈局中,如

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

如上,在name.xml和contract.xml中都可以使用變數user了

不支援在merge下直接使用include的情形,如下:

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

表示式語言

共同的特徵

表示式語言看起來很像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 ?:

例:

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

丟失的操作符

this、new、super 如這些,還是隻能在java類中使用

合併null操作符

如下使用 “??” 操作符

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

等價於:

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

屬性引用

如下,user有getLastName(),可以簡單引用:

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

避免NullPointerException

資料繫結程式碼會自動檢查null值,避免NullPointerException。像上文說過,會給定一個預設值

集合

一般的集合都可以使用”[]”操作符: 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&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

字串字面值

如下,可以使用單引號在屬性值上,用雙引號在字串字面值上:

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

也可以反過來使用:

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

當一個複數多個引數,:

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

一些資源需要顯式型別的評估:
這裡寫圖片描述

資料物件

任何POJO物件都可以用於資料繫結,但是更改POJO物件,並不會引起UI更新。有三種不同的資料更改通知機制:觀察物件,觀察欄位和觀察集合。當其中一個繫結到使用者介面的可觀察的資料物件,觀察到資料物件的屬性變化,使用者介面將自動更新。

觀察物件(Observable Objects)

A class implementing the Observable interface ,將被允許附加一個listener,來監聽物件所有屬性的改變

Observable interface有一個機制來新增和刪除listeners,但需要通知開發者。為了讓開發變得更容易,提供一個基類,BaseObservable,它實現建立listener的註冊機制。當屬性資料改變時,資料類實現者需要響應通知。這是通過在getter上使用一個註解@Bindable,並在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 class 。BR類檔案將生成在moudle的package下。如果資料類的基類不能被改變,Observable interface的實現類可以使用PropertyChangeRegistry儲存和有效地通知listeners。

觀察屬性(ObservableFields)

如果想做比建立上面的觀察物件更少的工作,那麼可以使用ObservableField 和它的同級的一些型別: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable。它們都是一個包含單一屬性的可觀察的物件。為避免裝箱、拆箱操作,可以在資料類中定義成 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();
}

如下,使用get或set來獲取和設定屬性值:

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

觀察集合(Observable Collections)

一些應用程式使用更多的動態結構來儲存資料。可觀察集合允許通過key訪問這些資料物件。當key是一個引用型別,如String,就可以用ObservableArrayMap。

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

在下面的佈局中,就可以通過String型別的key來訪問map中的資料:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</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"/>

當key是一個Integer時,可用ObservableArrayList:

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

佈局中使用ObservableArrayList:

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

生成Binding

生成的繫結類,會連結佈局的variables。正如前面所討論的,繫結的名稱和包可能是自定義的的。生成的所有繫結類都 extends ViewDataBinding。

建立

如前文所述,由相應的佈局檔案,而生成了相應的binding class。程式碼中使用它,需要LayoutInflater,如:

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

如果佈局已經inflate了,在某些場景時,可以單獨繫結:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時binding class 不能提前知道。在這種情況下,可以使用DataBindingUtil類建立繫結:

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

View中使用id

在佈局中的每個使用了id的view,都會在binding class中創建出一個同名的public屬性。這種繫結機制,比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 class中將會生成:

public final TextView firstName;
public final TextView lastName;

不使用data binding的view,可以沒有id,但有些view還是需要在程式碼中來訪問

Variable

將為每個variable生成getter和setter方法。如

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

上面的程式碼將生成:

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

ViewStub

  • ViewStub 這種view與普通的view有一點不同。它的特點是,在一開始,它是不可見的;在需要可見時,會由配置的另一個佈局來替換自身
  • 簡單的說,如果要使用資料繫結,那麼在ViewStub所配置的佈局inflate之前,該佈局就已建立好了data binding

高階繫結

動態Variable

有時,特定的繫結類不會為人所知。例如,一個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();
}

直接繫結

當一個variable or observable發生了改變,繫結框架會安排在下一幀進行檢視的改變。然而,有時希望立即發生改變。可以使用executePendingBindings()來強制執行

後臺執行緒

你可以改變你的資料模型在一個後臺執行緒,只要它不是一個集合。資料繫結框架將本地化每個變數和屬性,以避免任何併發問題。

Attribute Setters

當view使用了繫結表示式,只要繫結值發生變化,生成的繫結類必須呼叫相應的setter方法。定製資料繫結框架的方式方法呼叫設定值。資料繫結框架允許自定義setter方法。

自動Setters

對於一個attribute,資料繫結框架將試圖查詢對應的setAttribute()。它的namespace並不重要,只關注attribute的name。如,TextView中的屬性android:text使用了表示式,那麼框架就會查詢setText(String)。如果表示式返回的是int,那麼將會查詢setText(int)。所以,表示式需要返回正確的型別。資料繫結框架,支援建立一個佈局元素(View|ViewGroup)中,並不存在的屬性。
如下,生成的binding class中,將生成一個setDrawerListener(listener):

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

重新命名 Setters

有一些屬性的setter方法,與它的屬性名不匹配。這些屬性的setter可能會通過使用@BindingMethods,來進行重新命名。要求內問包含@BindingMethod。例如ImageView有一個屬性android:tint,它對應setter方法為setImageTintList(ColorStateList tint):

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

開發人員將不太可能需要重新命名setter,因android屬性框架已經實現。

自定義Setters

一些屬性需要自定義繫結邏輯。例如,屬性android:paddingLeft沒有對應的setter方法,而在view中有一個方法為setPadding(left, top, right, bottom)。可以使用@BindingAdapter來自定義一個關於屬性android:paddingLeft的setter。例:

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

@BindingAdapter也可以運用在自定義型別上。 當資料繫結框架建立的Binding adapter
與開發人員建立的有衝突時,會採用開發人員所建立的來替代。

還可以用Binding adapter來接收多個引數:

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

對應的xml繫結:

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

當ImageView中同時使用了屬性String imageUrl和Drawlable error,通過Binding adapter,這時的setter就是 loadImage()

注意:在匹配時,自定義namespace將被忽略。在編寫Binding adapter時,也可以使用android的namespace

下面的示例,表示預設採用oldPadding值,如果與newPadding不一致,則用newPadding:

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

事件處理繫結表示式,對應的Binding Adapter只能使用介面或有一個抽象方法的抽象類。例如:

@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()。我們必須建立兩個介面來區分的屬性和事件處理。

@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 Adapter:

@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) {