Bindungsadapter

Bindungsadapter sind dafür verantwortlich, die entsprechenden Framework-Aufrufe zum Festlegen von Werten auszuführen. Ein Beispiel ist das Festlegen eines Attributwerts, z. B. durch das Aufrufen der Methode setText(). Ein weiteres Beispiel ist das Festlegen eines Event-Listeners wie das Aufrufen der Methode setOnClickListener().

In der Datenbindungsbibliothek können Sie die aufgerufene Methode zum Festlegen eines Werts angeben, Ihre eigene Bindungslogik bereitstellen und den Typ des zurückgegebenen Objekts mithilfe von Adaptern angeben.

Attributwerte festlegen

Wenn sich ein Grenzwert ändert, muss die generierte Bindungsklasse eine Setter-Methode für die Ansicht mit dem Bindungsausdruck aufrufen. Sie können die Methode automatisch von der Datenbindungsbibliothek bestimmen lassen oder die Methode explizit deklarieren oder benutzerdefinierte Logik zur Auswahl einer Methode bereitstellen.

Automatische Auswahl der Methode

Bei einem Attribut namens example findet die Bibliothek automatisch die Methode setExample(arg), die kompatible Typen als Argument akzeptiert. Der Namespace des Attributs wird nicht berücksichtigt. Bei der Suche nach einer Methode werden nur der Attributname und der Attributtyp verwendet.

Mit dem Ausdruck android:text="@{user.name}" sucht die Bibliothek beispielsweise nach einer setText(arg)-Methode, die den von user.getName() zurückgegebenen Typ akzeptiert. Wenn der Rückgabetyp von user.getName() String ist, sucht die Bibliothek nach einer setText()-Methode, die ein String-Argument akzeptiert. Wenn der Ausdruck ein int zurückgibt, sucht die Bibliothek nach einer setText()-Methode, die ein int-Argument akzeptiert. Der Ausdruck muss den richtigen Typ zurückgeben. Bei Bedarf können Sie den Rückgabewert umwandeln.

Die Datenbindung funktioniert auch dann, wenn kein Attribut mit dem angegebenen Namen vorhanden ist. Mithilfe der Datenbindung können Sie Attribute für jeden Setter erstellen. Die Supportklasse DrawerLayout hat beispielsweise keine Attribute, dafür aber viele Setter. Im folgenden Layout werden automatisch die Methoden setScrimColor(int) und addDrawerListener(DrawerListener) als Setter für die Attribute app:scrimColor bzw. app:drawerListener verwendet:

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

Geben Sie einen Namen für die benutzerdefinierte Methode an

Einige Attribute haben Setter, die nicht mit dem Namen übereinstimmen. In diesen Situationen kann ein Attribut dem Setter mithilfe der Annotation BindingMethods zugeordnet werden. Die Annotation wird mit einer Klasse verwendet und kann mehrere BindingMethod-Annotationen enthalten, eine für jede umbenannte Methode. Bindungsmethoden sind Annotationen, die Sie einer beliebigen Klasse in Ihrer App hinzufügen können.

Im folgenden Beispiel ist das Attribut android:tint der Methode setImageTintList(ColorStateList) und nicht der Methode setTint() zugeordnet:

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

In der Regel müssen Sie Setter in Android-Framework-Klassen nicht umbenennen. Die Attribute sind bereits mithilfe der Namenskonvention implementiert, um automatisch übereinstimmende Methoden zu finden.

Benutzerdefinierte Logik bereitstellen

Einige Attribute erfordern eine benutzerdefinierte Bindungslogik. Für das Attribut android:paddingLeft ist beispielsweise kein Setter vorhanden. Stattdessen wird die Methode setPadding(left, top, right, bottom) bereitgestellt. Mit einer statischen Bindungsadaptermethode mit der Annotation BindingAdapter können Sie anpassen, wie ein Setter für ein Attribut aufgerufen wird.

Die Attribute der Android-Framework-Klassen haben bereits BindingAdapter-Annotationen. Das folgende Beispiel zeigt den Bindungsadapter für das Attribut 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());
}

Die Parametertypen sind wichtig. Der erste Parameter bestimmt die Art der Ansicht, die mit dem Attribut verknüpft ist. Der zweite Parameter bestimmt den Typ, der im Bindungsausdruck für das jeweilige Attribut akzeptiert wird.

Bindungsadapter sind auch für andere Arten der Anpassung nützlich. Beispielsweise kann ein benutzerdefiniertes Ladeprogramm aus einem Worker-Thread aufgerufen werden, um ein Image zu laden.

Sie können auch Adapter verwenden, die mehrere Attribute erhalten, wie im folgenden Beispiel gezeigt:

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

Sie können den Adapter in Ihrem Layout verwenden, wie im folgenden Beispiel gezeigt. Beachten Sie, dass @drawable/venueError auf eine Ressource in Ihrer Anwendung verweist. Wenn Sie die Ressource mit @{} umgeben, ist sie ein gültiger Bindungsausdruck.

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

Der Adapter wird aufgerufen, wenn imageUrl und error für ein ImageView-Objekt verwendet werden, imageUrl ein String und error ein Drawable ist. Wenn der Adapter aufgerufen werden soll, wenn eines der Attribute festgelegt ist, setzen Sie das optionale Flag requireAll des Adapters auf false, wie im folgenden Beispiel gezeigt:

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

Binding-Adaptermethoden können die alten Werte in ihren Handlern übernehmen. Eine Methode, die alte und neue Werte verwendet, muss zuerst alle alten Werte für die Attribute deklarieren, gefolgt von den neuen Werten, wie im folgenden Beispiel gezeigt:

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

Event-Handler können nur mit Schnittstellen oder abstrakten Klassen mit einer abstrakten Methode verwendet werden, wie im folgenden Beispiel gezeigt:

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

Verwenden Sie diesen Event-Handler wie folgt in Ihrem Layout:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

Wenn ein Listener über mehrere Methoden verfügt, muss er in mehrere Listener aufgeteilt werden. Für View.OnAttachStateChangeListener gibt es beispielsweise zwei Methoden: onViewAttachedToWindow(View) und onViewDetachedFromWindow(View). Die Bibliothek bietet zwei Schnittstellen, um die Attribute und Handler für sie zu unterscheiden:

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

Da sich Änderungen an einem Listener auf den anderen auswirken können, benötigen Sie einen Adapter, der entweder für eines der Attribute oder für beide funktioniert. Sie können requireAll in der Annotation auf false setzen, um anzugeben, dass nicht jedem Attribut ein Bindungsausdruck zugewiesen werden muss, wie im folgenden Beispiel gezeigt:

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

Das obige Beispiel ist etwas kompliziert, da die Klasse View die Methoden addOnAttachStateChangeListener() und removeOnAttachStateChangeListener() anstelle einer Setter-Methode für OnAttachStateChangeListener verwendet. Mit der Klasse android.databinding.adapters.ListenerUtil können Sie diese Listener im Blick behalten, damit sie im Bindungsadapter entfernt werden können.

Objekt-Conversions

Automatische Objektkonvertierung

Wenn ein Object von einem Bindungsausdruck zurückgegeben wird, wählt die Bibliothek die Methode aus, mit der der Wert des Attributs festgelegt wird. Object wird in einen Parametertyp der ausgewählten Methode umgewandelt. Dieses Verhalten ist in Anwendungen praktisch, die zum Speichern von Daten die Klasse ObservableMap verwenden, wie im folgenden Beispiel gezeigt:

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

Das userMap-Objekt im Ausdruck gibt einen Wert zurück, der automatisch in den Parametertyp umgewandelt wird, der in der Methode setText(CharSequence) gefunden wird, die zum Festlegen des Werts des Attributs android:text verwendet wird. Wenn der Parametertyp mehrdeutig ist, wandeln Sie den Rückgabetyp im Ausdruck um.

Benutzerdefinierte Conversions

In einigen Fällen ist für bestimmte Typen eine benutzerdefinierte Conversion erforderlich. Beispielsweise erwartet das Attribut android:background einer Ansicht einen Drawable-Wert, aber der angegebene Wert color ist eine Ganzzahl. Das folgende Beispiel zeigt ein Attribut, das einen Drawable erwartet, aber stattdessen eine Ganzzahl angegeben wird:

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

Wenn Drawable erwartet und eine Ganzzahl zurückgegeben wird, konvertieren Sie int in einen ColorDrawable. Verwenden Sie zum Ausführen der Konvertierung eine statische Methode mit der Annotation BindingConversion:

Kotlin

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

Java

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

Die im Bindungsausdruck angegebenen Werttypen müssen jedoch einheitlich sein. Sie können keine unterschiedlichen Typen in einem Ausdruck verwenden, wie im folgenden Beispiel gezeigt:

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

Weitere Informationen

Weitere Informationen zur Datenbindung finden Sie in den folgenden Ressourcen.

Produktproben

Codelabs

Blogposts