Dispositions et expressions de liaison

Le langage d'expression vous permet d'écrire des expressions qui gèrent les événements envoyés par les vues. La bibliothèque Data Binding génère automatiquement les classes requises pour lier les vues de la mise en page à vos objets de données.

Les fichiers de mise en page de la liaison de données sont légèrement différents et commencent par la balise racine layout, suivie d'un élément data et d'un élément racine view. Cet élément de vue correspond à votre racine dans un fichier de mise en page sans liaison. Le code suivant montre un exemple de fichier de mise en page:

<?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>

La variable user dans data décrit une propriété qui peut être utilisée dans cette mise en page:

<variable name="user" type="com.example.User" />

Les expressions de la mise en page sont écrites dans les propriétés d'attribut à l'aide de la syntaxe @{}. Dans l'exemple suivant, le texte TextView est défini sur la propriété firstName de la variable user:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

Objets de données

Supposons que vous disposiez d'un objet brut décrivant l'entité 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;
  }
}

Ce type d'objet comporte des données qui ne changent jamais. Il est courant dans les applications d'avoir des données qui sont lues une seule fois et qui ne changent jamais par la suite. Il est également possible d'utiliser un objet qui suit un ensemble de conventions, telles que les méthodes d'accesseur dans le langage de programmation Java, comme illustré dans l'exemple suivant:

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

Du point de vue de la liaison de données, ces deux classes sont équivalentes. L'expression @{user.firstName} utilisée pour l'attribut android:text accède au champ firstName de l'ancienne classe et à la méthode getFirstName() dans la seconde. Elle est également résolue en firstName(), si cette méthode existe.

Lier des données

Une classe de liaison est générée pour chaque fichier de mise en page. Par défaut, le nom de la classe est basé sur le nom du fichier de mise en page, converti en casse Pascal, avec le suffixe Binding ajouté. Par exemple, le nom de fichier de mise en page précédent est activity_main.xml. La classe de liaison générée correspondante est donc ActivityMainBinding.

Cette classe contient toutes les liaisons des propriétés de mise en page (par exemple, la variable user) aux vues de la mise en page et sait comment attribuer des valeurs aux expressions de liaison. Nous vous recommandons de créer les liaisons tout en gonflant la mise en page, comme illustré dans l'exemple suivant:

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

Au moment de l'exécution, l'application affiche l'utilisateur Test dans l'interface utilisateur. Vous pouvez également obtenir la vue à l'aide d'un LayoutInflater, comme illustré dans l'exemple suivant:

Kotlin

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

Si vous utilisez des éléments de liaison de données dans un adaptateur Fragment, ListView ou RecyclerView, vous pouvez utiliser les méthodes inflate() des classes de liaison ou la classe DataBindingUtil, comme illustré dans l'exemple de code suivant:

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

Langage d'expression

Fonctionnalités communes

Le langage d'expression ressemble beaucoup aux expressions que l'on trouve dans le code géré. Vous pouvez utiliser les opérateurs et les mots clés suivants dans le langage d'expression:

  • Mathématique: + - / * %
  • Concaténation de chaînes: +
  • Logique: && ||
  • Binaire: & | ^
  • Unaire: + - ! ~
  • Maj: >> >>> <<
  • Comparaison: == > < >= <= (< doit être échappé en tant que &lt;)
  • instanceof
  • Groupement : ()
  • Littéraux, comme "caractère", "Chaîne", "numérique" ou "null"
  • Caster
  • Appels de méthode
  • Accès aux champs
  • Accès au tableau: []
  • Opérateur ternaire: ?:

Voici quelques exemples :

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

Opérations manquantes

Les opérations suivantes ne sont pas indiquées dans la syntaxe d'expression que vous pouvez utiliser dans le code géré:

  • this
  • super
  • new
  • Appel générique explicite

Opérateur de coalisation nul

L'opérateur de coalisation nul (??) choisit l'opérande de gauche s'il n'est pas null ou l'opérande de droite si l'ancien est null:

android:text="@{user.displayName ?? user.lastName}"

D'un point de vue fonctionnel, cette méthode revient à utiliser:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

Références de propriété

Une expression peut référencer une propriété d'une classe au format suivant, qui est le même pour les champs, les getters et les objets ObservableField:

android:text="@{user.lastName}"

Éviter les exceptions de pointeur nul

Le code de liaison de données généré vérifie automatiquement les valeurs null et évite les exceptions de pointeur nul. Par exemple, dans l'expression @{user.name}, si user est nul, la valeur par défaut null est attribuée à user.name. Si vous faites référence à user.age, où l'âge est de type int, la liaison de données utilise la valeur par défaut de 0.

Afficher les références

Une expression peut référencer d'autres vues de la mise en page par ID, à l'aide de la syntaxe suivante:

android:text="@{exampleText.text}"

Dans l'exemple suivant, la vue TextView fait référence à une vue EditText dans la même mise en page:

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

Collections

Pour plus de commodité, vous pouvez accéder aux collections courantes, telles que les tableaux, les listes, les listes creuses et les cartes.[]

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;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]}"

Vous pouvez également faire référence à une valeur de la map en utilisant la notation object.key. Par exemple, vous pouvez remplacer @{map[key]} dans l'exemple précédent par @{map.key}.

Littéraux de chaîne

Vous pouvez utiliser des guillemets simples pour entourer la valeur de l'attribut, ce qui vous permet d'utiliser des guillemets doubles dans l'expression, comme illustré dans l'exemple suivant:

android:text='@{map["firstName"]}'

Vous pouvez également utiliser des guillemets doubles pour délimiter la valeur de l'attribut. Dans ce cas, les littéraux de chaîne doivent être entourés d'accents graves (`), comme indiqué ci-dessous:

android:text="@{map[`firstName`]}"

Ressources

Une expression peut référencer des ressources d'application avec la syntaxe suivante:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

Vous pouvez évaluer les chaînes de format et les pluriels en fournissant des paramètres:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

Vous pouvez transmettre des références de propriété et des références de vue en tant que paramètres de ressource:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

Lorsqu'un pluriel prend plusieurs paramètres, transmettez tous les paramètres:


  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

Certaines ressources nécessitent une évaluation explicite du type, comme indiqué dans le tableau suivant:

Type Référence normale Référence d'expression
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Gestion des événements

La liaison de données vous permet d'écrire des événements de gestion d'expression qui sont envoyés à partir des affichages (par exemple, la méthode onClick()). Les noms des attributs d'événement sont déterminés par le nom de la méthode d'écouteur, à quelques exceptions près. Par exemple, View.OnClickListener possède une méthode onClick(). L'attribut de cet événement est donc android:onClick.

Certains gestionnaires d'événements spécialisés pour l'événement de clic nécessitent un attribut autre que android:onClick pour éviter tout conflit. Vous pouvez utiliser les attributs suivants pour éviter ce type de conflit:

Classe Setter de l'écouteur Attribut
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Vous pouvez utiliser ces deux mécanismes, décrits en détail dans les sections suivantes, pour gérer un événement:

  • Références de méthode: dans vos expressions, vous pouvez référencer des méthodes conformes à la signature de la méthode d'écouteur. Lorsqu'une expression renvoie une référence de méthode, la liaison de données encapsule la référence de méthode et l'objet propriétaire dans un écouteur, et définit cet écouteur sur la vue cible. Si l'expression renvoie la valeur null, la liaison de données ne crée pas d'écouteur, mais définit un écouteur null à la place.
  • Listener binding: il s'agit d'expressions lambda qui sont évaluées lorsque l'événement se produit. La liaison de données crée toujours un écouteur, qu'elle définit sur la vue. Lorsque l'événement est déclenché, l'écouteur évalue l'expression lambda.

Références de méthodes

Vous pouvez lier directement des événements aux méthodes de gestionnaire, de la même manière que vous pouvez attribuer android:onClick à une méthode dans une activité. L'un des avantages par rapport à l'attribut onClick View est que l'expression est traitée au moment de la compilation. Ainsi, si la méthode n'existe pas ou que sa signature est incorrecte, vous recevez une erreur au moment de la compilation.

La principale différence entre les références de méthode et les expressions "listener binding" est que l'implémentation réelle de l'écouteur est créée lorsque les données sont liées, et non lorsque l'événement est déclenché. Si vous préférez évaluer l'expression lorsque l'événement se produit, utilisez listener bindings.

Pour attribuer un événement à son gestionnaire, utilisez une expression de liaison normale, dans laquelle la valeur correspond au nom de la méthode à appeler. Prenons l'exemple d'objet de données de mise en page suivant:

Kotlin

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

Java

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

L'expression de liaison peut attribuer l'écouteur de clics d'une vue à la méthode onClickFriend(), comme suit:

<?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:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

Expressions "listener binding"

Les expressions "listener binding" sont des expressions de liaison qui s'exécutent lorsqu'un événement se produit. Elles sont semblables aux références de méthodes, mais vous permettent d'exécuter des expressions de liaison de données arbitraires. Cette fonctionnalité est disponible avec le plug-in Android Gradle pour Gradle version 2.0 ou ultérieure.

Dans les références de méthode, les paramètres de la méthode doivent correspondre à ceux de l'écouteur d'événements. Dans les expressions "listener binding", seule la valeur renvoyée doit correspondre à la valeur renvoyée par l'écouteur, sauf s'il attend void. Prenons l'exemple de la classe de présentateur suivante, qui utilise une méthode onSaveClick():

Kotlin

class Presenter {
    fun onSaveClick(task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(Task task){}
}

Vous pouvez lier l'événement de clic à la méthode onSaveClick() comme suit:

<?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:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>

Lorsqu'un rappel est utilisé dans une expression, la liaison de données crée automatiquement l'écouteur nécessaire et l'enregistre pour l'événement. Lorsque la vue déclenche l'événement, la liaison de données évalue l'expression donnée. Comme pour les expressions de liaison régulière, vous obtenez la sécurité nulle et la sécurité des threads de la liaison de données pendant l'évaluation de ces expressions d'écouteur.

Dans l'exemple précédent, le paramètre view transmis à onClick(View) n'est pas défini. Les expressions "listener binding" offrent deux options pour les paramètres d'écouteur : vous pouvez ignorer tous les paramètres de la méthode ou tous les nommer. Si vous préférez nommer les paramètres, vous pouvez les utiliser dans votre expression. Par exemple, vous pouvez écrire l'expression précédente comme suit:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

Si vous souhaitez utiliser le paramètre dans l'expression, procédez comme suit:

Kotlin

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

Et vous pouvez utiliser une expression lambda avec plusieurs paramètres:

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:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

Si l'événement que vous écoutez renvoie une valeur dont le type n'est pas void, vos expressions doivent également renvoyer le même type de valeur. Par exemple, si vous souhaitez écouter l'événement d'appui de manière prolongée (clic long), votre expression doit renvoyer une valeur booléenne.

Kotlin

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Java

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

Si l'expression ne peut pas être évaluée en raison d'objets null, la liaison de données renvoie la valeur par défaut de ce type, par exemple null pour les types de référence, 0 pour int ou false pour boolean.

Si vous devez utiliser une expression avec un prédicat (par exemple, un ternaire), vous pouvez utiliser void comme symbole:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

Éviter les écouteurs complexes

Les expressions d'écouteur sont puissantes et peuvent rendre votre code plus facile à lire. En revanche, les écouteurs contenant des expressions complexes rendent vos mises en page plus difficiles à lire et à gérer. Faites en sorte que vos expressions soient aussi simples que de transmettre les données disponibles de votre UI à votre méthode de rappel. Implémentez n'importe quelle logique métier dans la méthode de rappel que vous appelez à partir de l'expression d'écouteur.

Importations, variables et inclusions

La bibliothèque Data Binding fournit des fonctionnalités telles que les importations, les variables et les inclusions. Les importations permettent de référencer facilement des classes dans vos fichiers de mise en page. Les variables vous permettent de décrire une propriété pouvant être utilisée dans les expressions de liaison. Les fonctionnalités d'inclusion vous permettent de réutiliser des mises en page complexes dans votre application.

Importations

Les importations vous permettent de référencer des classes dans votre fichier de mise en page, comme dans le code géré. Vous pouvez utiliser zéro ou plusieurs éléments import dans l'élément data. L'exemple de code suivant importe la classe View dans le fichier de mise en page:

<data>
    <import type="android.view.View"/>
</data>

Importer la classe View vous permet de la référencer à partir de vos expressions de liaison. L'exemple suivant montre comment référencer les constantes VISIBLE et GONE de la classe View:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

Alias de type

En cas de conflits de noms de classes, vous pouvez renommer l'une des classes en alias. L'exemple suivant renomme la classe View du package com.example.real.estate en Vista:

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

Vous pouvez ensuite utiliser Vista pour référencer com.example.real.estate.View et View pour référencer android.view.View dans le fichier de mise en page.

Importer d'autres classes

Vous pouvez utiliser des types importés comme références de type dans les variables et les expressions. L'exemple suivant montre User et List utilisés comme type de variable:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User>"/>
</data>

Vous pouvez utiliser les types importés pour caster une partie d'une expression. L'exemple suivant convertit la propriété connection en un type de User:

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Vous pouvez également utiliser des types importés lorsque vous faites référence à des champs et des méthodes statiques dans des expressions. Le code suivant importe la classe MyStringUtils et référence sa méthode 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"/>

Tout comme dans le code géré, java.lang.* est importé automatiquement.

Variables

Vous pouvez utiliser plusieurs éléments variable dans l'élément data. Chaque élément variable décrit une propriété qui peut être définie sur la mise en page à utiliser dans les expressions de liaison du fichier de mise en page. L'exemple suivant déclare les variables user, image et 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>

Les types de variables sont inspectés au moment de la compilation. Par conséquent, si une variable implémente Observable ou est une collection observable, cela doit être reflété dans le type. Si la variable est une classe ou une interface de base qui n'implémente pas l'interface Observable, les variables ne sont pas observées.

Lorsqu'il existe différents fichiers de mise en page pour différentes configurations (par exemple, paysage ou portrait), les variables sont combinées. Il ne doit pas y avoir de définitions de variables en conflit entre ces fichiers de mise en page.

La classe de liaison générée possède un setter et un getter pour chacune des variables décrites. Elles utilisent les valeurs de code géré par défaut jusqu'à ce que le setter soit appelé : null pour les types de référence, 0 pour int, false pour boolean, etc.

Une variable spéciale nommée context est générée pour être utilisée dans les expressions de liaison si nécessaire. La valeur de context est l'objet Context de la méthode getContext() de la vue racine. La variable context est remplacée par une déclaration de variable explicite portant ce nom.

Des capuchons de connexion

Vous pouvez transmettre des variables à la liaison d'une mise en page incluse à partir de la mise en page contenante en utilisant l'espace de noms de l'application et le nom de la variable dans un attribut. L'exemple suivant montre les variables user incluses dans les fichiers de mise en page name.xml et contact.xml:

<?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>

La liaison de données ne permet pas d'inclure une inclusion en tant qu'enfant direct d'un élément de fusion. Par exemple, la mise en page suivante n'est pas acceptée:

<?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>

Ressources supplémentaires

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

Exemples

Ateliers de programmation

Articles de blog