バインディング アダプター

バインディング アダプターは、適切なフレームワーク呼び出しを行う役割を担います。 設定します。たとえば、 setText() メソッドを使用します。その他 たとえば、イベント リスナーの設定です。 setOnClickListener() メソッドを呼び出します。

データ バインディング ライブラリを使用すると、値を設定するために呼び出されるメソッドを指定できます。 独自のバインディング ロジックを提供し、返されるオブジェクトの型を 使用できます。

属性値を設定する

バインド値が変更されるたびに、生成されたバインディング クラスでセッターを呼び出す必要がある メソッドをバインディング式でビューに適用します。データ バインディングでは、 このメソッドは、ライブラリが自動的に決定しますが、 メソッドを選択するためのカスタム ロジックを指定できます。

メソッドの自動選択

example という名前の属性の場合、ライブラリは自動的にメソッドを検索します。 互換性のある型を引数として受け入れる setExample(arg)。Namespace 考慮されません。属性名とタイプのみが使用されます。 メソッドを検索する際に表示されます。

たとえば、android:text="@{user.name}" 式の場合、ライブラリは 返された型を受け入れる setText(arg) メソッドを探します。 user.getName()user.getName() の戻り値の型が String の場合、 ライブラリが、String 引数を受け入れる setText() メソッドを探します。もし 式が int を返す場合、ライブラリは、次の setText() メソッドを検索します。 int 引数を受け入れます。この式は正しい型を返す必要があります。Google Chat では 必要に応じて戻り値をキャストします。

データ バインディングは、指定した名前の属性が存在しない場合でも機能します。Google Chat では データ バインディングを使用してセッターの属性を作成する。たとえば、 クラス DrawerLayout 属性はありませんが、多くのセッターがあります。次のレイアウト 自動的に setScrimColor(int) および addDrawerListener(DrawerListener) メソッド(app:scrimColorapp: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 属性が setImageTintList(ColorStateList) メソッドであり、setTint() メソッドとは異なります。

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 フレームワーク クラスのセッターの名前を変更する必要はありません。「 各属性は、名前規則を使用して自動的に実装され、 一致するメソッドを検索します。

カスタム ロジックを提供する

属性によっては、カスタムのバインディング ロジックが必要な場合もあります。たとえば、1 つのテーブルに android:paddingLeft 属性のセッター。その代わり、setPadding(left, top, right, bottom) メソッドが提供されています。静的バインディング アダプター メソッド BindingAdapter アノテーションを使用すると、属性のセッターの呼び出し方法をカスタマイズできます。

Android フレームワーク クラスの属性には、すでに BindingAdapter が含まれています。 アノテーション。次の例は、Cloud Storage バケットのバインディング アダプターを 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}" />

アダプターは、imageUrlerror がイベントに使用されると呼び出されます。 ImageView オブジェクト。imageUrlerror は、 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());
   }
}

イベント ハンドラをインターフェースまたは抽象クラスと併用できるのは、 抽象化メソッドを使用します。

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 には、次の 2 つのメソッドがあります。 onViewAttachedToWindow(View) および onViewDetachedFromWindow(View)。 ライブラリには、属性とハンドラを区別するための 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);
}

一方のリスナーを変更すると他方のリスナーにも影響が及ぶ可能性があるため、 どちらかの属性、または両方の属性で機能します。requireAllfalse に設定できます すべての属性にバインディングを割り当てる必要がないことを示すアノテーション 使用できます。

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 クラスは addOnAttachStateChangeListener() および removeOnAttachStateChangeListener() メソッドをセッター メソッドの代わりに使用し、 OnAttachStateChangeListenerandroid.databinding.adapters.ListenerUtil クラスは、これらのイベントを追跡するのに役立ちます。 バインディング アダプターで削除できます。

オブジェクトの変換

オブジェクトの自動変換

Object がバインディングから返された場合 式を指定すると、ライブラリは、変数の値を プロパティです。Object は、選択されたメソッドのパラメータ型にキャストされます。この 動作は、 ObservableMap クラスを データを保存する必要があります

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

式の userMap オブジェクトは値を返します。これは、 setText(CharSequence) メソッドにあるパラメータ型にキャストします。 android:text 属性の値を設定します。パラメータの型が あいまいな場合は、式の戻り値の型をキャストします。

カスタム変換

場合によっては、特定の型間でのカスタム変換が必要になります。対象 たとえば、ビューの 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

ブログ投稿