式言語を使用すると、ビューによってディスパッチされたイベントを処理する式を作成できます。データ バインディング ライブラリは、レイアウト内のビューをデータ オブジェクトにバインドするために必要なクラスを自動的に生成します。
データ バインディングのレイアウト ファイルは少し異なっており、layout
のルートタグで始まり、その後に data
要素と 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 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
エンティティを記述するプレーンなオブジェクトがあるとします。
Kotlin
data class User(val firstName: String, val lastName: String)
Java
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
この型のオブジェクトには、変更されることがないデータが格納されます。アプリでは 1 回読み取られ、その後変更されないデータを使用するのが一般的です。また、次の例に示すように、Java プログラミング言語でアクセサ メソッドを使用するなど、一連の規則に従ったオブジェクトを使用することもできます。
Kotlin
// Not applicable in Kotlin. data class User(val firstName: String, val lastName: String)
Java
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 つのクラスは同等です。android:text
属性に使用される式 @{user.firstName}
は、前者のクラスの firstName
フィールドと後者のクラスの getFirstName()
メソッドにアクセスします。また、このメソッドが存在する場合は firstName()
に解決されます。
データのバインド
バインディング クラスはレイアウト ファイルごとに生成されます。デフォルトでは、クラス名はレイアウト ファイルの名前に基づいてパスカルケースに変換され、Binding 接尾辞が付加されたものになります。たとえば、上記のレイアウト ファイル名は activity_main.xml
であるため、対応する生成されたバインディング クラスは ActivityMainBinding
になります。
このクラスは、レイアウト プロパティ(user
変数など)からレイアウトのビューへのすべてのバインディングを保持し、バインディング式の値を割り当てる方法を認識します。次の例に示すように、レイアウトをインフレートしながらバインディングを作成することをおすすめします。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.user = User("Test", "User") }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Test", "User"); binding.setUser(user); }
実行時に、アプリの UI にテストユーザーが表示されます。また、次の例に示すように、LayoutInflater
を使用してビューを取得することもできます。
Kotlin
val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
Java
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
Fragment
、ListView
、RecyclerView
アダプター内でデータ バインディング アイテムを使用する場合は、次のコードサンプルに示すように、バインディング クラスまたは DataBindingUtil
クラスの inflate()
メソッドを使用することをおすすめします。
Kotlin
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) // or val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
Java
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
式言語
一般的な機能
式言語はマネージコードの式によく似ています。式言語では、次の演算子とキーワードを使用できます。
- 数学:
+ - / * %
- 文字列の連結:
+
- 論理:
&& ||
- バイナリ:
& | ^
- 単項:
+ - ! ~
- シフト:
>> >>> <<
- 比較:
== > < >= <=
(<
は<
としてエスケープする必要があります) instanceof
- グループ:
()
- リテラル(文字、文字列、数値、
null
など) - キャスト
- メソッド呼び出し
- フィールド アクセス
- 配列アクセス:
[]
- 3 項演算子:
?:
以下の例をご参照ください。
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
使用できない演算
次のオペレーションは、マネージド コードで使用できる式の構文にありません。
this
super
new
- 明示的な汎用呼び出し
null 合体演算子
null 合体演算子(??
)は、左のオペランドが null
でない場合は左のオペランドを、左のオペランドが null
の場合は右のオペランドを選択します。
android:text="@{user.displayName ?? user.lastName}"
これは、機能的には以下と同等です。
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
プロパティ参照
次の形式を使用して、式でクラス内のプロパティを参照できます。これは、フィールド、ゲッター、ObservableField
オブジェクトの場合と同じです。
android:text="@{user.lastName}"
null ポインタ例外を回避する
生成されたデータ バインディング コードは、null
値を自動的にチェックし、null ポインタ例外を回避します。たとえば、式 @{user.name}
では、user
が null の場合、user.name
にデフォルト値の null
が割り当てられます。user.age
を参照する場合(年齢が int
の場合)、データ バインディングではデフォルト値の 0
が使用されます。
ビューの参照
次の構文を使用すると、式から ID を使用してレイアウト内の他のビューを参照できます。
android:text="@{exampleText.text}"
次の例では、TextView
ビューは同じレイアウト内の EditText
ビューを参照しています。
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
コレクション
利便性のため []
演算子を使用すると、配列、リスト、スパースリスト、マップなどの一般的なコレクションにアクセスできます。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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]}"
object.key
表記を使用して、マップ内の値を参照することもできます。たとえば、上記の例の @{map[key]}
は @{map.key}
に置き換えます。
文字列リテラル
次の例に示すように、属性値を一重引用符で囲むと、式内で二重引用符を使用できます。
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)}"
プロパティの参照とビュー参照をリソース パラメータとして渡すことができます。
android:text="@{@string/example_resource(user.lastName, exampleText.text)}"
複数形が複数のパラメータを取る場合は、すべてのパラメータを渡します。
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 |
イベント処理
データ バインディングを使用すると、ビューからディスパッチされるイベントを処理する式(onClick()
メソッドなど)を作成できます。イベント属性名は、リスナー メソッドの名前によって決まりますが、例外があります。たとえば、View.OnClickListener
に onClick()
メソッドがあるため、このイベントの属性は android:onClick
です。
クリック イベント用の特別なイベント ハンドラの中には、競合を避けるために android:onClick
以外の属性を必要とするものがあります。次の属性を使用すると、このような競合を回避できます。
クラス | リスナーのセッター | 属性 |
---|---|---|
SearchView |
setOnSearchClickListener(View.OnClickListener) |
android:onSearchClick |
ZoomControls |
setOnZoomInClickListener(View.OnClickListener) |
android:onZoomIn |
ZoomControls |
setOnZoomOutClickListener(View.OnClickListener) |
android:onZoomOut |
この 2 つのメカニズムを使用して、イベントを処理します。以降のセクションで詳しく説明します。
- メソッド参照: 式では、リスナー メソッドのシグネチャに準拠するメソッドを参照できます。式がメソッド参照と評価されると、データ バインディングによってリスナーのメソッド参照とオーナー オブジェクトがラップされ、そのリスナーがターゲット ビューに設定されます。式が
null
と評価された場合、データ バインディングでリスナーは作成されず、null
リスナーが設定されます。 - リスナー バインディング: イベントが発生したときに評価されるラムダ式です。データ バインディングは常にリスナーを作成し、それをビューに設定します。イベントがディスパッチされると、リスナーはラムダ式を評価します。
メソッド参照
アクティビティのメソッドに android:onClick
を割り当てるのと同じように、イベントをハンドラ メソッドに直接バインドできます。View
onClick
属性と比較した場合の利点の一つは、式がコンパイル時に処理されることです。そのため、メソッドが存在しないか、シグネチャが正しくない場合は、コンパイル時エラーが発生します。
メソッド参照とリスナー バインディングの主な違いは、イベントがトリガーされたときではなく、データがバインドされたときに実際のリスナー実装が作成される点です。イベントが発生したときに式を評価する場合は、リスナー バインディングを使用します。
イベントをハンドラに割り当てるには、通常のバインディング式を使用します。その際、呼び出すメソッド名を指定します。たとえば、次のレイアウト データ オブジェクトの例について考えてみましょう。
Kotlin
class MyHandlers { fun onClickFriend(view: View) { ... } }
Java
public class MyHandlers { public void onClickFriend(View view) { ... } }
このバインディング式では、次のようにビューのクリック リスナーを onClickFriend()
メソッドに割り当てることができます。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:
</LinearLayout>
</layout>
リスナー バインディング
リスナー バインディングは、イベントの発生時に実行されるバインディング式です。メソッド参照に似ていますが、任意のデータ バインディング式を実行できます。この機能は、Android Gradle プラグイン バージョン 2.0 以降で使用できます。
メソッド参照では、メソッドのパラメータはイベント リスナーのパラメータと一致する必要があります。リスナー バインディングでは、リスナーの期待される戻り値と一致する必要があるのは、void
がある場合を除き、戻り値だけになります。たとえば、onSaveClick()
メソッドを持つ次のプレゼンター クラスについて考えてみましょう。
Kotlin
class Presenter { fun onSaveClick(task: Task){} }
Java
public class Presenter { public void onSaveClick(Task task){} }
次のようにして、クリック イベントを onSaveClick()
メソッドにバインドできます。
<?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: -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
式でコールバックを使用すると、データ バインディングによって必要なリスナーが自動的に作成され、イベントに登録されます。ビューがイベントを起動すると、データ バインディングで指定された式が評価されます。正規表現バインディング式と同様に、これらのリスナー式の評価中に、データ バインディングの null とスレッドの安全性を取得します。
上記の例では、onClick(View)
に渡される view
パラメータが定義されていません。リスナー バインディングには、リスナー パラメータの 2 つの選択肢があります。メソッドへのすべてのパラメータを無視するか、すべてのパラメータに名前を付けることができます。パラメータに名前を付ける場合は、式で使用できます。たとえば、上記の式は次のように記述できます。
android: -> presenter.onSaveClick(task)}"
このパラメータを式で使用する場合は、次のようにします。
Kotlin
class Presenter { fun onSaveClick(view: View, task: Task){} }
Java
public class Presenter { public void onSaveClick(View view, Task task){} }
android: -> presenter.onSaveClick(theView, task)}"
また、複数のパラメータを持つラムダ式を使用できます。
Kotlin
class Presenter { fun onCompletedChanged(task: Task, completed: Boolean){} }
Java
public class Presenter { public void onCompletedChanged(Task task, boolean completed){} }
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android: isChecked) -> presenter.completeChanged(task, isChecked)}" />
リッスンしているイベントが void
以外の型の値を返す場合、式でも同じ型の値を返す必要があります。たとえば、長押し(長押し)イベントをリッスンする場合は、式でブール値を返す必要があります。
Kotlin
class Presenter { fun onLongClick(view: View, task: Task): Boolean { } }
Java
public class Presenter { public boolean onLongClick(View view, Task task) { } }
android: -> presenter.onLongClick(theView, task)}"
null
オブジェクトが原因で式を評価できない場合、データ バインディングはその型のデフォルト値を返します(参照型の場合は null
、int
の場合は 0
、boolean
の場合は false
など)。
3 項など、述語付きの式を使用する必要がある場合は、void
をシンボルとして使用できます。
android: -> v.isVisible() ? doSomething() : void}"
複雑なリスナーを使用しない
リスナー式は強力で、コードを読みやすくします。一方、リスナーに複雑な式が含まれている場合、レイアウトの読み取りとメンテナンスが難しくなります。利用可能なデータを UI からコールバック メソッドに渡すだけで、式がシンプルになります。リスナー式から呼び出すコールバック メソッド内に、ビジネス ロジックを実装します。
インポート、変数、インクルード
データ バインディング ライブラリは、インポート、変数、インクルードなどの機能を提供します。インポートを使用すると、レイアウト ファイル内のクラスを簡単に参照できるようになります。変数を使用すると、バインディング式で使用できるプロパティを記述できます。インクルードを使用すると、複雑なレイアウトをアプリ全体で再利用できます。
インポート
インポートを使用すると、マネージドコードのように、レイアウト ファイル内のクラスを参照できます。data
要素内では 0 個以上の import
要素を使用できます。次のコードサンプルでは、View
クラスをレイアウト ファイルにインポートしています。
<data>
<import type="android.view.View"/>
</data>
View
クラスをインポートすると、バインディング式から参照できます。次の例は、View
クラスの定数 VISIBLE
と GONE
を参照する方法を示しています。
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
型のエイリアス
クラス名が競合する場合は、一方のクラスの名前をエイリアスに変更できます。次の例では、com.example.real.estate
パッケージの View
クラスの名前を Vista
に変更します。
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
その後、Vista
を使用して com.example.real.estate.View
と View
を参照し、レイアウト ファイル内の android.view.View
を参照できます。
他のクラスをインポートする
インポートされた型は、変数や式の型参照として使用できます。次の例は、変数の型として使用される User
と List
を示しています。
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
インポートされた型を使用して、式の一部をキャストできます。次の例では、connection
プロパティを User
型にキャストしています。
<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.lang.*
は自動的にインポートされます。
変数
data
要素内で複数の variable
要素を使用できます。各 variable
要素には、レイアウト ファイル内のバインディング式で使用できるレイアウトに設定できるプロパティを記述します。次の例では、user
変数、image
変数、note
変数を宣言しています。
<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
インターフェースを実装していない基本クラスまたはインターフェースの場合、変数は監視されません。
構成(横向きや縦向きなど)ごとに異なるレイアウト ファイルがある場合は、変数が結合されます。これらのレイアウト ファイル間で変数定義が競合しないようにする必要があります。
生成されるバインディング クラスには、記述された変数ごとにセッターとゲッターがあります。セッターが呼び出されるまで、変数はデフォルトのマネージド コード値を使用します(参照型の場合は null
、int
の場合は 0
、boolean
の場合は false
など)。
必要に応じて、バインディング式で使用する context
という名前の特別な変数が生成されます。context
の値は、ルートビューの getContext()
メソッドの Context
オブジェクトです。context
変数は、その名前での明示的な変数宣言によってオーバーライドされます。
インクルード
アプリの名前空間と属性で変数名を使用することで、含まれるレイアウトからインクルードされたレイアウトのバインディングに変数を渡すことが可能です。次の例は、name.xml
および contact.xml
レイアウト ファイルからインクルードされた user
変数を示しています。
<?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>
データ バインディングでは、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><!-- Doesn't work -->
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
参考情報
データ バインディングの詳細については、以下の参考情報をご覧ください。