バインディング アダプターは、値を設定するための適切なフレームワーク呼び出しを行います。一例としては、setText()
メソッドを呼び出すなど、プロパティ値の設定があります。別の例として、setOnClickListener()
メソッドを呼び出すなど、イベント リスナーを設定することが挙げられます。
データ バインディング ライブラリを使用すると、アダプターを使用して、値を設定するために呼び出されるメソッドの指定、独自のバインディング ロジックの提供、返されるオブジェクトの型を指定できます。
属性値を設定する
バインドされた値が変更されるたびに、生成されるバインディング クラスは、バインディング式を使用してビューのセッター メソッドを呼び出す必要があります。データ バインディング ライブラリで自動的にメソッドを決定することも、メソッドを明示的に宣言するか、メソッドを選択するカスタム ロジックを用意することもできます。
メソッドの自動選択
example
という名前の属性の場合、互換性のある型を引数として受け入れるメソッド setExample(arg)
がライブラリによって自動的に検出されます。属性の名前空間は考慮されません。メソッドの検索時には属性の名前と型のみが使用されます。
たとえば、android:text="@{user.name}"
式を指定すると、ライブラリは user.getName()
から返される型を受け入れる setText(arg)
メソッドを探します。user.getName()
の戻り値の型が String
の場合、ライブラリは String
引数を受け入れる setText()
メソッドを探します。式が int
を返した場合、ライブラリは int
引数を受け入れる setText()
メソッドを検索します。式は正しい型を返す必要があります。必要に応じて戻り値をキャストできます。
データ バインディングは、指定した名前の属性が存在しない場合でも機能します。データ バインディングを使用して、任意のセッターの属性を作成できます。たとえば、サポートクラス DrawerLayout
には属性はありませんが、セッターは多数あります。次のレイアウトでは、setScrimColor(int)
メソッドと addDrawerListener(DrawerListener)
メソッドがそれぞれ app:scrimColor
属性と app:drawerListener
属性のセッターとして自動的に使用されます。
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
カスタムのメソッド名を指定する
一部の属性には、名前で照合できないセッターがあります。このような場合、BindingMethods
アノテーションを使用して属性をセッターに関連付けることができます。このアノテーションはクラスで使用され、複数の BindingMethod
アノテーション(名前が変更されたメソッドごとに 1 つ)を含めることができます。バインディング メソッドは、アプリの任意のクラスに追加できるアノテーションです。
次の例では、android:tint
属性は setTint()
メソッドではなく、setImageTintList(ColorStateList)
メソッドに関連付けられています。
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
通常は、Android フレームワーク クラスのセッターの名前を変更する必要はありません。属性は、一致するメソッドを自動的に検出するための名前規則を使用してすでに実装されています。
カスタム ロジックを提供する
属性によっては、カスタムのバインディング ロジックが必要な場合もあります。たとえば、android:paddingLeft
属性にはセッターが関連付けられていません。その代わり、setPadding(left,
top, right, bottom)
メソッドが提供されています。BindingAdapter
アノテーション付きの静的バインディング アダプター メソッドを使用すると、属性のセッターを呼び出す方法をカスタマイズできます。
Android フレームワーク クラスの属性には、すでに BindingAdapter
アノテーションがあります。次の例は、paddingLeft
属性のバインディング アダプターを示しています。
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
パラメータの型は重要です。最初のパラメータにより、属性に関連付けられるビューの型が決まります。2 番目のパラメータにより、指定された属性のバインディング式で受け入れられる型が決まります。
バインディング アダプターは他の種類のカスタマイズにも便利です。たとえば、ワーカー スレッドからカスタム ローダーを呼び出して、画像を読み込むことができます。
次の例に示すように、複数の属性を受け取るアダプターを作成することもできます。
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
次の例に示すように、レイアウト内でアダプタを使用できます。@drawable/venueError
はアプリ内のリソースを参照しています。リソースを @{}
で囲むことで、有効なバインディング式になります。
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
ImageView
オブジェクトに imageUrl
と error
が使用され、imageUrl
が文字列、error
が Drawable
の場合、アダプタが呼び出されます。いずれかの属性が設定されたときにアダプタが呼び出されるようにする場合は、次の例に示すように、アダプタのオプションの requireAll
フラグを false
に設定します。
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
バインディング アダプター メソッドは、ハンドラで古い値を受け取ることができます。古い値と新しい値を取得するメソッドは、次の例に示すように、最初に属性の古い値をすべて宣言してから、新しい値を続けます。
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@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()); } }
イベント ハンドラは、次の例に示すように、1 つの抽象メソッドを持つインターフェースまたは抽象クラスでのみ使用できます。
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@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 android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
リスナーに複数のメソッドがある場合は、複数のリスナーに分割する必要があります。たとえば、View.OnAttachStateChangeListener
には、onViewAttachedToWindow(View)
と onViewDetachedFromWindow(View)
の 2 つのメソッドがあります。このライブラリには、属性とハンドラを区別するための 2 つのインターフェースが用意されています。
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
一方のリスナーを変更するともう一方のリスナーに影響する可能性があるため、いずれかの属性または両方で機能するアダプターが必要になります。アノテーションで requireAll
を false
に設定すると、すべての属性にバインディング式を割り当てる必要がないことを指定できます。次の例をご覧ください。
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { 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); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
上記の例は、View
クラスが OnAttachStateChangeListener
のセッター メソッドの代わりに addOnAttachStateChangeListener()
メソッドと removeOnAttachStateChangeListener()
メソッドを使用しているため、やや複雑です。android.databinding.adapters.ListenerUtil
クラスはこれらのリスナーを追跡し、バインディング アダプターで削除できるようにします。
オブジェクトの変換
オブジェクトの自動変換
バインディング式から Object
が返されると、ライブラリは、プロパティの値の設定に使用されるメソッドを選択します。Object
は、選択されたメソッドのパラメータ型にキャストされます。この動作は、次の例に示すように、ObservableMap
クラスを使用してデータを保存するアプリで便利です。
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
式の userMap
オブジェクトは値を返します。この値は、android:text
属性の値の設定に使用される setText(CharSequence)
メソッドにあるパラメータ型に自動的にキャストされます。パラメータの型があいまいな場合は、戻り値の型を式にキャストします。
カスタム変換
場合によっては、特定の型間でのカスタム変換が必要になります。たとえば、ビューの android:background
属性には Drawable
が必要ですが、指定された color
値は整数です。次の例では、属性に Drawable
が必要ですが、代わりに整数が指定されています。
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Drawable
が想定され、整数が返される場合は、int
を ColorDrawable
に変換します。変換を行うには、次のように BindingConversion
アノテーション付きの静的メソッドを使用します。
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
ただし、バインディング式に指定する値の型は一貫している必要があります。次の例に示すように、同じ式内で異なる型を使用することはできません。
// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
参考情報
データ バインディングの詳細については、次のリソースをご覧ください。
サンプル
Codelab
ブログ投稿
あなたへのおすすめ
- 注: JavaScript がオフになっている場合はリンクテキストが表示されます
- データ バインディング ライブラリ
- レイアウトとバインディング式
- ビュー バインディング