Skip to content

Most visited

Recently visited

navigation

データ バインディング ライブラリ

このドキュメントでは、宣言的なレイアウトを作成し、アプリケーション ロジックとレイアウトをバインドするのに必要なコードを最小限に抑えるためのデータ バインディング ライブラリの使い方を説明しています。

データ バインディング ライブラリは柔軟性が高く、幅広い互換性があります。サポート ライブラリのため、Android 2.1(API レベル 7 以降)のすべての Android プラットフォーム バージョンでお使いいただけます。

データ バインディングを使うには、Android Plugin for Gradle 1.5.0-alpha1 以降が必要です。

ビルド環境

データ バインディングを開始するには、まず Android SDK Manager のサポート リポジトリからライブラリをダウンロードします。

アプリでデータバイディングを使用するよう設定するには、dataBinding 要素をアプリ モジュール内の build.gradle ファイルに追加します。

設定に使うコード スニペットは次のとおりです。

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

データ バインディングを使うライブラリに依存するアプリ モジュールがある場合は、そのアプリの build.gradle 内でもデータ バイディングの設定をする必要があります。

また、必ず互換性のある Android Studio のバージョンを使用してください。Android Studio でのデータ バインディングのサポートで説明しているように、Android Studio 1.3 以降では、データ バイディングがサポートされています。

データ バインディングのレイアウト ファイル

はじめてのデータ バインディング式を記述する

データ バインディング用のレイアウト ファイルは通常のレイアウト ファイルとは若干異なっており、最初にルート要素の layout タグがあり、その下に data要素、view のルート要素と続きます。この 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 で、このレイアウトで使用するプロパティを記述します。

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

データ オブジェクト

ここでは、User クラスに Plain Old Java Object (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;
   }
}

データ バインディングの観点では、この 2 つのクラスは同等です。TextView の android:text 属性に使われる @{user.firstName} 式は、先のクラス内で firstName フィールドに、後のクラス内で getFirstName() メソッドにアクセスすることになります。または、firstName() メソッドが存在すれば、そのメソッドに解決されます。

データのバインディング

デフォルトでは、レイアウト ファイルの名前をパスカルケースに変換して、末尾に "Binding" をつけたものがバインディングクラスになります。上のレイアウト ファイルは main_activity.xml なので、MainActivityBinding クラスが作成されます。このクラスにはレイアウト プロパティ(user 変数)からレイアウトのビューまでのすべてのバインディングが含まれ、バインディング式に値を適用する方法が指定されています。バインディングの作成はインフレート中に行うのが最も簡単です。

@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 で Test User が見えることを確認してください。次のようにしても確認できます。

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

ListView または RecyclerView アダプタ内でデータ バインディング アイテムを使う場合には、こちらの方法を使用することをお勧めします。

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

イベントの処理

データバイディングでは、onClick イベントなどのビューから送られたイベントを処理する式を記述することができます。いくつかの例外を除いて、イベント属性名はリスナー メソッドの名前によって決まります。たとえば、View.OnLongClickListener のメソッドは onLongClick() なので、このイベント属性は android:onLongClick になります。イベントを扱うには 2 つの方法があります。

メソッド参照

イベントは、ハンドラ メソッドに直接バインドできます。android:onClick をアクティビティのメソッドに割り当てる方法と同様です。View#onClick 属性と比べた場合の主な長所の 1 つは、コンパイル時に式が処理されるため、メソッドが存在しない、あるいは署名が正しくない場合にはコンパイル時エラーが出ることです。

メソッド参照とリスナー バインディングとの主な違いは、イベントの発生時ではなくデータのバインド時に、実際にリスナーが生成されるという点です。イベント発生時に式を評価したい場合には、リスナー バインディングを使用してください。

ハンドラにイベントを割り当てるには、通常のバインディング式で呼び出すメソッド名の値を使用します。たとえば、データ オブジェクトに 2 つのメソッドがあれば次のようになります。

public class MyHandlers {
    public void onClickFriend(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="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

式に使うメソッドの署名は、リスナー オブジェクトで使うメソッドの署名と同一でなければならない点に注意してください。

リスナー バインディング

リスナー バインディングはイベント発生時に実行するバインディング式です。メソッド参照と似ていますが、こちらは任意のデータ バインディング式を実行可能です。この機能は Android Gradle Plugin for Gradle version 2.0 以降で利用できます。

メソッド参照では、メソッドのパラメータがイベント リスナーのパラメータと一致している必要がありますが、リスナー バインディングでは、戻り値が想定されるリスナーの戻り値と一致すれば問題ありません(void を想定する場合を除く)。たとえば、次のようなメソッドを持つ Presenter クラスを使用できます。

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>

リスナーは、式のルート要素としてのみ許可されたラムダ式で表されます。式の中でコールバックが使われている場合、データ バインディングでは自動的に必要なリスナーを作成して、対応するイベントに登録します。ビューがイベントを発生させると、データ バインディングでは指定した式が評価されます。通常のバインディング式と同じく、リスナー式の評価中に null やデータ バインディングのスレッドセーフが返される場合があります。

上記の例では、onClick(android.view.View) に渡す view パラメータを定義していない点に注目してください。リスナー バインディングでは、リスナー パラメータに関して 2 つの選択肢があります。メソッドに対するパラメータをすべて無視するか、すべて名前を付けるかです。パラメータに名前を付けると、それらを自身の式で使用できます。たとえば、上記の式は次のように書けます。

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"
式の中でパラメータを使い場合は、次のようになります。
public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
パラメータを 2 つ以上の持つラムダ式も使用できます。
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)}" />

void 型ではない値を返すイベントをリッスンしている場合、式でも同じ型の値を返さなければなりません。たとえば、長押しクリック イベントをリッスンしたい場合、式は boolean を返す必要があります。

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

null オブジェクトによって評価ができない場合、データ バインディングでは型に応じて Java のデフォルト値が返されます。たとえば、参照型なら nullint 型なら 0boolean 型なら false が返されます。

述語を伴う式(3 値論理など)を使用する必要がある場合は、シンボルとして void を使うことができます。

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
複雑なリスナー使用しない
リスナー式は強力で、コードを非常に読みやすくすることができます。一方でリスナーに複雑な式が含まれていると、レイアウトの可読性が下がり、管理が大変になります。利用可能なデータを UI からコールバック メソッドに渡せるよう、式は簡潔でなければなりません。リスナー式から呼び出したコールバック メソッド内に、ビジネス ロジックを実装する必要があります。

特別なクリック イベント ハンドラが複数存在しますが、競合を避けるために android:onClick 以外の属性が必要です。競合を回避するために作成された属性は次のとおりです。

クラス リスナー セッター 属性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

レイアウトの詳細

インポート

data 要素内で、ゼロ個以上の import 要素を使用できます。これにより Java と同様に、レイアウト ファイル内のクラスを簡単に参照できます。

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

これで、ビューをバインディング式の中で使用できます。

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

クラス名が競合している場合、そのうちの 1 つが "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 を参照します。インポートされたタイプは、変数と式におけるタイプ リファレンスにも使います。

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

注: Android Studio はまだインポートに対応していないため、インポートされた変数に対するオートコンプリートがお使いの IDE で機能しない可能性があります。それでもアプリは変数定義の完全修飾名を使うことで、正常にコンパイルを行い、IDE の問題を回避することができます。

<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.* は自動的にインポートされます。

変数

data 要素内では、いくつでも 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>

変数の型はコンパイル時にチェックされるため、変数が Observable を実装する、あるいはオブザーバブル コレクションである場合には型に反映する必要があります。変数が基底クラスあるいは Observable* インターフェースを実装しない場合、変数を監視されません。

横向きや縦向きなどのさまざまな状況に応じて異なるレイアウト ファイルがある場合、変数は結合されます。これらのレイアウト ファイル間での変数定義の競合を避ける必要があります。

生成したバインディング クラスには、規定された変数ごとにセッターとゲッターがあります。参照型には — nullint 型には 0boolean には false などのように、セッターが呼び出されるまでは、Java のデフォルト値を変数に使います。

必要に応じて、バインディング式で使用する context という特殊変数が生成されます。context の値は、ルートビューの getContext()Context です。context 変数は、この名前で明示的に変数を宣言することでオーバーライドされます。

カスタム バインディング クラスの名前

デフォルトでは、バインディング クラスは、レイアウト ファイル名を基に生成されます。まずレイアウト ファイル名の最初の文字を大文字にして、アンダースコアを削除し、後に続く文字を大文字にして、末尾に "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>

インクルード

アプリの名前空間と属性の変数名を使用すると、それらを含むレイアウトからインクルードされたレイアウト バインディングに変数が渡されます。

<?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 の両方のレイアウト ファイル内になければなりません。

データ バインディングでは、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>

式言語

共通機能

式言語は、Java の式によく似ています。次のものは共通です。

例:

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

非対応の演算

Java で使用可能な式の構文のうち、以下の演算は非対応になっています。

null 合体演算子

null 合体演算子(??)は、null でなければ左のオペランドを、null なら右のオペランドを選択します。

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

機能としては次のものと同等です。

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

プロパティ参照

最初のものは、はじめてのデータ バインディング式を記述するで説明したとおり、JavaBean 参照の短縮形式です。式がクラス上のプロパティを参照するときには、フィールド、ゲッター、 ObservableField に対して同じ形式を使います。

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

NullPointerException を回避する

生成されたデータ バインディングのコードでは自動的に null チェックを行い、NullPointerException を回避します。たとえば、@{user.name} の式内で user が null であれば、user.name にはデフォルト値(null)が指定されます。user.age を参照して年齢が int であった場合は、デフォルトで 0 になります。

コレクション

一般的なコレクションである配列、リスト、疎なリスト、マップには、便宜上、演算子 [] を使ってアクセスします。

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

明示的な型の評価が必要なソースもあります。

通常のリファレンス 式のリファレンス
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

データ オブジェクト

旧式の Java オブジェクト(POKO)をデータ バインディングに利用することはできますが、POJO を修正しても UI はアップデートされません。データ バインディングの真価は、データ変更を通知する機能をデータ オブジェクトに付与することにあります。データ変更を通知する方法には、オブザーバブル オブジェクトオブザーバブル フィールドオブザーバブル コレクションがあります。

このうち 1 つのオブザーバブル データ オブジェクトが UI にバインドされてデータ オブジェクトのプロパティが変更されると、UI が自動的にアップデートされます。

オブザーバブル オブジェクト

クラス内に Observable インターフェースを実装することによって、バインディング時に単一のリスナーをバインディング オブジェクトに追加できるようになり、オブジェクト上のプロパティの変更をすべてリッスンすることが可能になります。

Observable インターフェースにはリスナーを追加および削除するメカニズムが備わっていますが、通知についてはデベロッパーにゆだねられています。基底クラスの BaseObservable は、リスナー登録の仕組みを簡単に実装するために作成されたものです。プロパティ変更の通知については、引き続きデータクラスの実装者が対応する必要があります。具体的には、Bindable アノテーションをゲッターに指定し、セッターに通知します。

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 クラスファイルはモジュール パッケージ内に生成されます。データクラスの基底クラスを変更できない場合は、効率的にリスナーを保存して通知するために、Observable インターフェースは便宜上 PropertyChangeRegistry を使って実装します。

オブザーバブル フィールド

Observable クラスの作成には多少手間がかかるため、時間を短縮したい、あるいはほとんどプロパティを使用しないデベロッパーの方は、ObservableField および兄弟関係にある ObservableBooleanObservableByteObservableCharObservableShortObservableIntObservableLongObservableFloatObservableDoubleObservableParcelable をお使いください。ObservableFields は単一のフィールドを持つ内蔵型のオブザーバブル オブジェクトです。プリミティブ バージョンでは、アクセス処理中のボックス化またはボックス化解除を回避できます。使用するには、データクラスに public final フィールドを作成します。

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

これで完了です。この値を使用するには、このセットを使ってアクセサ メソッドを取得します。

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

オブザーバブル コレクション

データを保持するために、より動的な構造体を使用するアプリもあります。オブザーバブル コレクションでは、キーを使用してデータ オブジェクトにアクセスできます。キーが文字列などの参照型の場合には ObservableArrayMap が役立ちます。

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

以下のレイアウトでは、文字列キーによって地図にアクセスします。

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

キーが整数の場合には ObservableArrayList が便利です。

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

以下のレイアウトでは、インデックスによってリストにアクセスします。

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

バインディングの生成

生成されたバインディング クラスは、レイアウト変数とレイアウト内のビューをリンクします。前述のとおり、バインディングの名前とパッケージはカスタマイズできます。生成されたバインディング クラスはすべて ViewDataBinding を拡張します。

作成

レイアウト内の式でビューにバインディングする前に、ビュー階層が崩れないようにするため、バインディングの作成はインフレーションの直後に行う必要があります。レイアウトへのバインディング方法は複数あり、最も一般的なのはバインディング クラス上で静的メソッドを使う方法です。インフレート メソッドは、ビュー階層をインフレートしてすべてバインディングするまでを一度に行います。LayoutInflater のみを使う方法や、ViewGroup を使うものなど、よりシンプルな方法もあります。

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

異なるメカニズムを使ってレイアウトをインフレートすると、別々にバインディングされます。

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

バインディングについて事前に分からない場合でも、DataBindingUtil クラスを使うとバインディングを作成できます。

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

ID 付きビュー

レイアウト内の ID を持つ各ビューに対して、public final フィールドが生成されます。バインディングはビュー階層上で単一パスを実行し、ID でビューを抽出します。このメカニズムの方が複数のビューに対して 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 は不要ですが、場合によってはコードからビューにアクセスする必要があります。

変数

それぞれの変数にアクセサ メソッドを指定します。

<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 は通常のビューと若干異なります。このビューは最初は非表示になっており、可視化または明示的にインフレートするよう指示された際に、他のビューをインフレートしてレイアウト内に置き換えます。

基本的に ViewStub はビュー階層からは見えないため、コレクションを行うにはバインディング オブジェクト内のビューも同じ状態にしなければなりません。ビューは final 型なので、ViewStubProxy オブジェクトは ViewStub に取って代わり、ViewStub が存在すればそのアクセス権を、ViewStub がインフレートされていれば、インフレートされたビュー階層へのアクセス権もデベロッパーに付与します。

他のレイアウトをインフレートする際には、新しいレイアウト用にバインディングを作成しなければなりません。そのため、ViewStubProxyViewStubViewStub.OnInflateListener をリッスンして、バインディングを作成する必要があります。ViewStubProxy は 1 つしか存在できないため、デベロッパーはこれに OnInflateListener を設定して、バインディングの作成後に呼び出すようにすることができます。

高度なバインディング

動的変数

具体的なバインディング クラスが不明な場合もあります。たとえば、任意のレイアウトに対して RecyclerView.Adapter を使用した場合、特定のバインディング クラスは分かりません。また、onBindViewHolder(VH, int) の処理中にバインディング値を割り当てる必要があります。

この例では、RecyclerView がバインドする全レイアウトに "item" 変数が含まれます。BindingHoldergetBinding メソッドは、基底クラス ViewDataBinding を返します。

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

即時にバインディング

変数やオブザーバブルが変更されると、次のフレームの前にバインディングがスケジュールされます。ただし、直ちにバインディングを実行する必要がある場合もあります。強制的に実行するには、executePendingBindings() メソッドを使用してください。

バックグラウンド スレッド

コレクションでない限り、データモデルをバックグラウンド スレッドで変更できます。データ バインディングでは同時実行の問題を回避するために、評価中に変数やフィールドをローカライズします。

属性のセッター

バインドされた値が変更されると、生成されたバインディング クラスは、バインディング式を使用してビュー上でセッター メソッドを呼び出す必要があります。データ バインディング フレームワークには、値を設定するために呼び出すメソッドをカスタマイズする方法が備わっています。

自動セッター

データ バインディングでは、属性に対して setAttribute メソッドを探します。考慮するのは属性の名前空間ではなく、属性の名前そのものです。

たとえば、TextView 属性に関連づけられた式である android:text は setText(String) を探します。式が int を返せば、データ バインディングは setText(int) メソッドを探します。式が正しい型を返すように注意し、必要に応じてキャストしてください。データ バインディングは、任意の名前を持つ属性が存在しなくても実行されます。よってデータ バインディングを使用することで、セッター用の属性を容易に作成することができます。たとえば、サポート ライブラリの DrawerLayout には属性がありませんが、セッターは多数あります。自動セッターを使うと、その中の 1 つを使用することができます。

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

セッターのリネーム

名前で検索できないセッターを備えた属性もあります。こうしたメソッドに対しては、BindingMethods アノテーションを介して属性をセッターに関連付けます。これはクラスと関連付けられており、リネームされた各メソッドの BindingMethod アノテーションを含んでいる必要があります。たとえば android:tint 属性は、実際は setTint ではなく setImageTintList(ColorStateList) に関連付けられます。

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

Android フレームワークの属性はすでに実装されているため、デベロッパーがセッターをリネームする必要はほとんどありません。

カスタム セッター

カスタム バインディング ロジックが必要な属性もあります。たとえば、android:paddingLeft 属性には関連付けられているセッターがなく、代わりに setPadding(left, top, right, bottom) が存在します。BindingAdapter アノテーションを備えた静的バインディング アダプタ メソッドを使用すると、デベロッパーは属性のセッターの呼び出し方をカスタマイズできます。

Android 属性には、すでに作成済みの BindingAdapter があります。たとえば、以下は paddingLeft に対応するものです。

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

バインディング アダプタは他のタイプのカスタマイズに役立ちます。たとえば、スレッド外でカスタム ローダを呼び出して画像を読み込むことができます。

競合が発生した場合は、デベロッパーが作成したバインディング アダプタが、データ バインディングのデフォルト アダプタをオーバーライドします。

複数のパラメータを受信できるアダプタも使用できます。

@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 に imageUrlerror が両方使われており、imageUrl が文字列で、error がドローアブルであれば、このアダプタが呼び出されます。

バインディング アダプタ メソッドはオプションで、ハンドラ内で古い値をとります。新旧の両方の値をとるメソッドでは、古い属性の値を最初に、そのあとに新しい値を続ける必要があります。

@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() の 2 つのメソッドがあります。この場合、属性とハンドラを区別するために 2 つのインターフェースを作成する必要があります。

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

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

片方のリスナーを変更すると、もう一方にも影響が生じるため、異なる 3 つのバインディング アダプタが必要です。属性それぞれに対応するものと両方に対応するものが両方設定されなければなりません。

@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.OnAttachStateChangeListener 用の set メソッドの代わりに、add および remove をリスナーに使うため、通常よりも少し複雑になっています。android.databinding.adapters.ListenerUtil クラスを使用すると、以前のリスナーを追跡して、バインディング アダプタ内で削除できます。

インターフェースの OnViewDetachedFromWindowOnViewAttachedToWindow@TargetApi(VERSION_CODES.HONEYCOMB_MR1) でアノテートすることによって、Honeycomb MR1 および addOnAttachStateChangeListener(View.OnAttachStateChangeListener) がサポートしているのと同じバージョンの新しい端末上でのみ、リスナーを呼び出す必要があるということをデータ バインディングのコード ジェネレータが認識します。

変換

オブジェクト変換

オブジェクトがバインディング式から返されると、自動セッター、リネームされたセッター、カスタム セッターのうちの 1 つが選ばれます。オブジェクトは、選ばれたセッターのパラメータの型にキャストされます。

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 を予期している状況で整数が返される場合は、必ず intColorDrawable に変換する必要があります。この変換には BindingConversion アノテーションを含む静的メソッドを使用します。

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

この変換はセッター レベルでのみ行われることにご注意ください。そのため、次のように型を混同することはできません

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

Android Studio でのデータ バインディングのサポート

Android Studio では、データ バインディング コード用のコード編集機能が多数サポートされています。たとえば、データ バインディング式には次のような機能を利用できます。

注: 配列やジェネリック型Observable クラスなど)は、エラーがないときでもエラーを表示します。

データ バインディング式が提供されると、プレビューのペインにはデフォルト値が表示されます。次の例はレイアウト XML ファイルから要素を抜粋したのもので、プレビューのペインには TextView 内のデフォルトのテキスト値 PLACEHOLDER が表示されます。

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

プロジェクトの設計段階でデフォルト値を表示する必要がある場合は、デフォルトの式の値ではなく、ツール属性を使うこともできます。詳細は Designtime Layout Attributes をご覧ください。

This site uses cookies to store your preferences for site-specific language and display options.

Hooray!

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.