Adaptateurs de liaison

Les adaptateurs de liaison sont chargés d'effectuer les appels de framework appropriés définir des valeurs. Vous pouvez, par exemple, définir une valeur de propriété, setText(). Autre consiste, par exemple, à définir un écouteur d'événements, setOnClickListener() .

La bibliothèque Data Binding vous permet de spécifier la méthode appelée pour définir une valeur, fournir votre propre logique de liaison et spécifier le type de l'objet renvoyé à l'aide d'adaptateurs.

Définir les valeurs des attributs

Chaque fois qu'une valeur liée change, la classe de liaison générée doit appeler un setter sur la vue avec l'expression de liaison. Vous pouvez laisser la liaison de données la bibliothèque détermine automatiquement la méthode, ou vous pouvez déclarer explicitement ou fournissez une logique personnalisée pour sélectionner une méthode.

Sélection automatique de la méthode

Pour un attribut nommé example, la bibliothèque trouve automatiquement la méthode setExample(arg) qui accepte les types compatibles comme argument. Espace de noms de l'attribut n'est pas prise en compte. Seuls le nom et le type de l'attribut sont utilisés lorsque vous recherchez une méthode.

Par exemple, pour l'expression android:text="@{user.name}", la bibliothèque recherche une méthode setText(arg) qui accepte le type renvoyé par user.getName() Si le type renvoyé de user.getName() est String, recherche une méthode setText() qui accepte un argument String. Si le renvoie un int, la bibliothèque recherche une méthode setText() qui accepte un argument int. L'expression doit renvoyer le type correct. Vous pouvez si nécessaire, castez la valeur renvoyée.

La liaison de données fonctionne même si aucun attribut du nom donné n'existe. Vous pouvez créer des attributs pour n'importe quel setter en utilisant la liaison de données. Par exemple, l'assistance cours DrawerLayout n'a pas d'attributs, mais il a beaucoup de setters. La mise en page suivante utilise automatiquement setScrimColor(int) et addDrawerListener(DrawerListener) comme setter pour app:scrimColor et app:drawerListener , respectivement:

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

Spécifier un nom de méthode personnalisé

Certains attributs ont des setters qui ne correspondent pas par nom. Dans ces situations, un peut être associé au setter à l'aide du BindingMethods . L'annotation est utilisée avec une classe et peut contenir plusieurs BindingMethod des annotations, une pour chaque méthode renommée. Les méthodes de liaison sont des annotations que vous pouvez ajouter à n'importe quel cours de votre application.

Dans l'exemple suivant, l'attribut android:tint est associé au setImageTintList(ColorStateList) , mais pas avec la méthode 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"),
})

En règle générale, vous n'avez pas besoin de renommer les setters dans les classes du framework Android. La sont déjà mis en œuvre selon la convention de nommage trouver des méthodes de correspondance.

Fournir une logique personnalisée

Certains attributs nécessitent une logique de liaison personnalisée. Par exemple, il n'existe aucun Setter de l'attribut android:paddingLeft À la place, la méthode setPadding(left, top, right, bottom) est fournie. Une méthode d'adaptateur de liaison statique avec le BindingAdapter vous permet de personnaliser l'appel d'un setter pour un attribut.

Les attributs des classes du framework Android disposent déjà de BindingAdapter . L'exemple suivant montre l'adaptateur de liaison pour 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());
}

Les types de paramètres sont importants. Le premier paramètre détermine le type la vue associée à l'attribut. Le deuxième paramètre détermine Type accepté dans l'expression de liaison de l'attribut donné.

Les adaptateurs de liaison sont également utiles pour d'autres types de personnalisation. Par exemple : Un chargeur personnalisé peut être appelé à partir d'un thread de nœud de calcul pour charger une image.

Vous pouvez également avoir des adaptateurs qui reçoivent plusieurs attributs, comme indiqué dans le l'exemple suivant:

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

Vous pouvez utiliser l'adaptateur dans votre mise en page, comme illustré dans l'exemple suivant. Remarque que @drawable/venueError fait référence à une ressource de votre application. Autour du avec @{} en fait une expression de liaison valide.

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

L'adaptateur est appelé si imageUrl et error sont utilisés pour une objet ImageView, imageUrl est chaîne, et error est une Drawable. Si vous voulez l'adaptateur à appeler lorsque l'un des attributs est défini, définissez le paramètre requireAll de l'adaptateur à false, comme illustré dans l'exemple suivant:

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

Les méthodes d'adaptateur de liaison peuvent utiliser les anciennes valeurs dans leurs gestionnaires. Une méthode l'utilisation des anciennes et des nouvelles valeurs doit d'abord déclarer toutes les anciennes valeurs pour les attributs, suivi des nouvelles valeurs, comme illustré dans l'exemple suivant:

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

Les gestionnaires d'événements ne peuvent être utilisés qu'avec des interfaces ou des classes abstraites avec une seule méthode abstraite, comme illustré dans l'exemple suivant:

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

Utilisez ce gestionnaire d'événements dans votre mise en page comme suit:

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

Lorsqu'un écouteur comporte plusieurs méthodes, il doit être divisé en plusieurs écouteurs. Par exemple : View.OnAttachStateChangeListener propose deux méthodes: onViewAttachedToWindow(View) et onViewDetachedFromWindow(View) La bibliothèque fournit deux interfaces pour différencier les attributs et les gestionnaires pour eux:

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

Étant donné que la modification d'un écouteur peut affecter l'autre, vous avez besoin d'un adaptateur qui fonctionne pour l'un ou l'autre de ces attributs ou pour les deux. Vous pouvez définir requireAll sur false dans Annotation permettant de spécifier qu'une liaison ne doit pas être attribuée à tous les attributs , comme illustré dans l'exemple suivant:

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

L'exemple ci-dessus est un peu compliqué, car le La classe View utilise la addOnAttachStateChangeListener() et removeOnAttachStateChangeListener() au lieu d'une méthode setter pour OnAttachStateChangeListener. La classe android.databinding.adapters.ListenerUtil permet d'effectuer un suivi de ces éléments. afin qu'ils puissent être supprimés de l'adaptateur de liaison.

Conversions d'objets

Conversion automatique d'objets

Lorsqu'un Object est renvoyé par une liaison , la bibliothèque sélectionne la méthode utilisée pour définir la valeur de la . Object est converti en type de paramètre de la méthode choisie. Ce est pratique dans les applications qui utilisent classe ObservableMap pour stocker des données, comme illustré dans l'exemple suivant:

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

L'objet userMap de l'expression renvoie une valeur, qui est automatiquement au type de paramètre trouvé dans la méthode setText(CharSequence) utilisée pour définissez la valeur de l'attribut android:text. Si le type de paramètre est ambigu, convertissez le type renvoyé dans l'expression.

Conversions personnalisées

Dans certains cas, une conversion personnalisée est requise entre des types spécifiques. Pour Par exemple, l'attribut android:background d'une vue attend un Drawable, mais la valeur color spécifiée est un entier. L'exemple suivant illustre une qui attend un Drawable, mais un entier est fourni à la place:

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

Chaque fois qu'une Drawable est attendue et qu'un entier est renvoyé, convertissez l'int à un ColorDrawable. Pour effectuer la conversion, utilisez une méthode statique avec une BindingConversion comme suit:

Kotlin

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

Java

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

Cependant, les types de valeurs fournis dans l'expression de liaison doivent être cohérents. Vous ne pouvez pas utiliser différents types dans la même expression, comme illustré ci-dessous Exemple:

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

Ressources supplémentaires

Pour en savoir plus sur la liaison de données, consultez les ressources suivantes.

Exemples

Ateliers de programmation

Articles de blog