Diseños y expresiones vinculantes

El lenguaje de expresiones te permite escribir expresiones que controlan eventos enviados por vistas. La biblioteca de vinculación de datos genera automáticamente las clases necesarias para vincular las vistas del diseño con tus objetos de datos.

Los archivos de diseño de la vinculación de datos son ligeramente diferentes y comienzan con una etiqueta raíz de layout, seguida de un elemento de data y un elemento raíz de view. Este elemento de vista corresponde a la raíz en un archivo de diseño no vinculante. En el siguiente código, se muestra un archivo de diseño de muestra:

<?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 dentro de data describe una propiedad que se puede usar en este diseño:

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

Las expresiones del diseño se escriben en las propiedades del atributo con la sintaxis @{}. En el siguiente ejemplo, el texto TextView se establece en la propiedad firstName de la variable user:

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

Objetos de datos

Supongamos que tienes un objeto sin formato para describir la entidad 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;
  }
}

Este tipo de objeto tiene datos que nunca cambian. En las apps, es común tener datos que se leen una vez y nunca cambian. También es posible usar un objeto que siga un conjunto de convenciones, como el uso de métodos de acceso en el lenguaje de programación Java, como se muestra en el siguiente ejemplo:

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

Desde la perspectiva de la vinculación de datos, esas dos clases son equivalentes. La expresión @{user.firstName} que se usa para el atributo android:text accede al campo firstName en la primera clase y al método getFirstName() en la segunda. También se resuelve en firstName(), si ese método existe.

Cómo vincular datos

Para cada archivo de diseño, se genera una clase de vinculación. De forma predeterminada, el nombre de la clase se basa en el nombre del archivo de diseño, convertido en mayúsculas y minúsculas, con el sufijo Binding. Por ejemplo, el nombre de archivo de diseño anterior es activity_main.xml, por lo que la clase de vinculación generada correspondiente es ActivityMainBinding.

Esta clase contiene todas las vinculaciones, desde las propiedades de diseño (por ejemplo, la variable user) hasta las vistas del diseño, y sabe cómo asignar valores para las expresiones de vinculación. Te recomendamos crear las vinculaciones mientras se aumenta el diseño, como se muestra en el siguiente ejemplo:

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

Durante el tiempo de ejecución, la app muestra el usuario de prueba en la IU. Como alternativa, puedes obtener la vista utilizando un LayoutInflater, como se muestra en el siguiente ejemplo:

Kotlin

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

Java

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

Si usas elementos de vinculación de datos dentro de un adaptador Fragment, ListView o RecyclerView, es posible que prefieras usar los métodos inflate() de las clases de vinculación o la clase DataBindingUtil, como se muestra en el siguiente ejemplo de código:

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

Lenguaje de expresiones

Funciones comunes

El lenguaje de expresiones se parece mucho a las expresiones que se encuentran en el código administrado. Puedes usar los siguientes operadores y palabras clave en el lenguaje de expresiones:

  • Matemáticos: + - / * %
  • Concatenación de cadenas: +
  • Lógico: && ||
  • Binario: & | ^
  • Unario: + - ! ~
  • Mayúsculas: >> >>> <<
  • Comparación: == > < >= <= (< debe tener el formato de escape &lt;)
  • instanceof
  • Agrupamiento: ()
  • Literales, como caracteres, strings, números o null
  • Transmisión
  • de llamadas a métodos
  • de acceso de campo
  • Acceso al array: []
  • Operador ternario: ?:

Estos son algunos ejemplos:

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

Operaciones faltantes

Faltan las siguientes operaciones de la sintaxis de expresión que puedes usar en el código administrado:

  • this
  • super
  • new
  • invocación genérica explícita

Operador coalescente nulo

El operador coalescente nulo (??) elige el operando izquierdo si no es null o el derecho si el primero es null:

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

Esto es funcionalmente equivalente a lo siguiente:

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

Referencias de propiedades

Una expresión puede hacer referencia a una propiedad en una clase mediante el siguiente formato, que es el mismo para campos, métodos get y objetos ObservableField:

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

Cómo evitar excepciones de puntero nulo

El código de vinculación de datos generado verifica automáticamente si hay valores null y evita las excepciones de puntero nulo. Por ejemplo, en la expresión @{user.name}, si user es nulo, a user.name se le asigna su valor predeterminado de null. Si haces referencia a user.age, donde la edad es de tipo int, la vinculación de datos usa el valor predeterminado de 0.

Referencias de vistas

Una expresión puede hacer referencia a otras vistas en el diseño por ID con la siguiente sintaxis:

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

En el siguiente ejemplo, la vista TextView hace referencia a una vista EditText en el mismo diseño:

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

Colecciones

Puedes acceder a colecciones comunes, como arrays, listas, listas dispersas y mapas, con el operador [] para mayor comodidad.

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

También puedes hacer referencia a un valor en el mapa con la notación object.key. Por ejemplo, puedes reemplazar @{map[key]} en el ejemplo anterior por @{map.key}.

Literales de string

Puedes usar comillas simples para rodear el valor del atributo, lo que te permite usar comillas dobles en la expresión, como se muestra en el siguiente ejemplo:

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

También puedes usar comillas dobles para rodear el valor del atributo. Cuando lo hagas, los literales de string deben estar rodeados de acentos graves `, como se muestra a continuación:

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

Recursos

Una expresión puede hacer referencia a los recursos de la app con la siguiente sintaxis:

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

Puedes evaluar las strings de formato y los valores plurales proporcionando parámetros:

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

Puedes pasar referencias de propiedades y referencias de vistas como parámetros de recursos:

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

Cuando un plural tome varios parámetros, pasa todos los parámetros:


  Have an orange
  Have %d oranges

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

Algunos recursos requieren una evaluación de tipos explícito, como se muestra en la siguiente tabla:

Tipo Referencia normal Referencia de expresiones
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Manejo de eventos

La vinculación de datos te permite escribir eventos de control de expresiones que se despachan desde las vistas, por ejemplo, el método onClick(). Los nombres de los atributos de los eventos están determinados por el nombre del método del objeto de escucha, con algunas excepciones. Por ejemplo, View.OnClickListener tiene un método onClick(), de manera que el atributo para este evento es android:onClick.

Hay algunos controladores de eventos especializados para el evento de clic que necesitan un atributo distinto de android:onClick para evitar un conflicto. Puedes usar los siguientes atributos para evitar este tipo de conflictos:

Clase Método set de objetos de escucha Atributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Puedes usar estos dos mecanismos, descritos en detalle en las siguientes secciones, para controlar un evento:

  • Referencias de métodos: En tus expresiones, puedes hacer referencia a métodos que se ajusten a la firma del método del objeto de escucha. Cuando una expresión se evalúa como una referencia de método, la vinculación de datos une la referencia del método y el objeto de propietario en un objeto de escucha y establece ese objeto de escucha en la vista de destino. Si la expresión se evalúa como null, la vinculación de datos no crea un objeto de escucha y establece un objeto de escucha null en su lugar.
  • Vinculaciones de objetos de escucha: Son expresiones lambda que se evalúan cuando ocurre el evento. La vinculación de datos siempre crea un objeto de escucha, que se establece en la vista. Cuando se envía el evento, el objeto de escucha evalúa la expresión lambda.

Referencias de métodos

Puedes vincular eventos a los métodos del controlador directamente, de manera similar a como se asigna android:onClick a un método en una actividad. Una ventaja en comparación con el atributo onClick de View es que la expresión se procesa en el tiempo de compilación. Por lo tanto, si el método no existe o su firma es incorrecta, recibirás un error de tiempo de compilación.

La principal diferencia entre las referencias de métodos y las vinculaciones de objetos de escucha es que la implementación real del objeto de escucha se crea cuando se vinculan los datos, no cuando se activa el evento. Si prefieres evaluar la expresión cuando ocurra el evento, usa las vinculaciones de objetos de escucha.

Para asignar un evento a su controlador, usa una expresión de vinculación normal; el valor debe ser el nombre del método que se llamará. Por ejemplo, considera el siguiente objeto de datos de diseño de ejemplo:

Kotlin

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

Java

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

La expresión de vinculación puede asignar el objeto de escucha de clics de una vista al método onClickFriend() de la siguiente manera:

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

Vinculaciones de objetos de escucha

Las vinculaciones de los objetos de escucha son expresiones de vinculación que se ejecutan cuando ocurre un evento. Son similares a las referencias de métodos, pero te permiten ejecutar expresiones de vinculación de datos arbitrarias. Esta función está disponible con el complemento de Android para Gradle 2.0 y versiones posteriores.

En las referencias de métodos, los parámetros del método deben coincidir con los parámetros del objeto de escucha de eventos. En las vinculaciones de objetos de escucha, solo el valor que se muestra debe coincidir con el valor que se muestra esperado del objeto de escucha, a menos que esté esperando void. Por ejemplo, considera la siguiente clase de presentador que tiene un método onSaveClick():

Kotlin

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

Java

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

Puedes vincular el evento de clic al método onSaveClick() de la siguiente manera:

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

Cuando se usa una devolución de llamada en una expresión, la vinculación de datos crea automáticamente el objeto de escucha necesario y lo registra para el evento. Cuando la vista activa el evento, la vinculación de datos evalúa la expresión dada. Al igual que con las expresiones de vinculación regulares, obtienes la seguridad nula y del subproceso de la vinculación de datos mientras se evalúan estas expresiones de los objetos de escucha.

En el ejemplo anterior, no se definió el parámetro view que se pasa a onClick(View). Las vinculaciones de objetos de escucha proporcionan dos opciones para sus parámetros: puedes ignorar todos los parámetros del método o nombrarlos a todos. Si prefieres nombrar los parámetros, puedes usarlos en tu expresión. Por ejemplo, puedes escribir la expresión anterior de la siguiente manera:

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

Si quieres usar el parámetro en la expresión, puedes hacerlo de la siguiente manera:

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

Además, puedes usar una expresión lambda con más de un parámetro:

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 el evento que estás escuchando muestra un valor cuyo tipo no es void, tus expresiones también deben mostrar el mismo tipo de valor. Por ejemplo, si deseas escuchar el evento de mantener presionado (clic largo), tu expresión debe mostrar un valor booleano.

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 la expresión no se puede evaluar debido a objetos null, la vinculación de datos muestra el valor predeterminado para ese tipo, como null para los tipos de referencia, 0 para int o false para boolean.

Si necesitas usar una expresión con un predicado, por ejemplo, un ternario, puedes usar void como símbolo:

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

Evita usar objetos de escucha complejos

Las expresiones de los objetos de escucha son potentes y pueden facilitar la lectura de tu código. Por otro lado, los objetos de escucha que contienen expresiones complejas dificultan la lectura y el mantenimiento de tus diseños más. Haz que tus expresiones sean tan simples como pasar los datos disponibles de tu IU a tu método de devolución de llamada. Implementa cualquier lógica empresarial dentro del método de devolución de llamada que invoques desde la expresión del objeto de escucha.

Importaciones, variables e inclusiones

La biblioteca de vinculación de datos proporciona funciones como importaciones, variables e inclusiones. Las importaciones hacen que las clases sean fáciles de consultar dentro de tus archivos de diseño. Las variables te permiten describir una propiedad que se puede usar en expresiones de vinculación. Las inclusiones permiten reutilizar diseños complejos en tu app.

Importaciones

Las importaciones te permiten hacer referencia a clases dentro de tu archivo de diseño, como en el código administrado. Puedes usar cero o más elementos import dentro del elemento data. En el siguiente ejemplo de código, se importa la clase View al archivo de diseño:

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

Importar la clase View te permite hacer referencia a ella desde tus expresiones de vinculación. En el siguiente ejemplo, se muestra cómo hacer referencia a las constantes VISIBLE y GONE de la clase View:

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

Escribe alias

Cuando hay conflictos de nombre de clase, puedes cambiar el nombre de una de las clases por un alias. En el siguiente ejemplo, se cambia el nombre de la clase View en el paquete com.example.real.estate a Vista:

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

Luego, puedes usar Vista para hacer referencia a com.example.real.estate.View y View para hacer referencia a android.view.View dentro del archivo de diseño.

Importa otras clases

Puedes usar tipos importados como referencias de tipo en variables y expresiones. En el siguiente ejemplo, se muestran los elementos User y List que se usan como el tipo de una 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>

Puedes usar los tipos importados para transmitir parte de una expresión. En el siguiente ejemplo, se convierte la propiedad connection a un tipo de User:

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

También puedes usar tipos importados cuando haces referencia a campos y métodos estáticos en las expresiones. En el siguiente código, se importa la clase MyStringUtils y se hace referencia a su método 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"/>

Al igual que en el código administrado, se importa automáticamente java.lang.*.

Variables

Puedes usar varios elementos variable dentro del elemento data. Cada elemento variable describe una propiedad que se puede configurar en el diseño para usar en expresiones de vinculación dentro del archivo de diseño. En el siguiente ejemplo, se declaran las variables user, image y 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>

Los tipos de variables se inspeccionan en el tiempo de compilación, por lo que, si una variable implementa Observable o es una colección observable, esto debe reflejarse en el tipo. Si la variable es una interfaz o clase base que no implementa la interfaz Observable, no se observan las variables.

Cuando hay diferentes archivos de diseño para varias configuraciones (por ejemplo, horizontal o vertical), las variables se combinan. No debe haber definiciones de variables conflictivas entre estos archivos de diseño.

La clase de vinculación generada tiene un método set y un método get para cada una de las variables descritas. Las variables toman los valores de código administrados predeterminados hasta que se llama al método set: null para los tipos de referencia, 0 para int, false para boolean, etcétera.

Se genera una variable especial llamada context para usar en expresiones de vinculación según sea necesario. El valor de context es el objeto Context del método getContext() de la vista raíz. La variable context se anula con una declaración de variable explícita con ese nombre.

Inclusiones

Puedes pasar variables a la vinculación de un diseño incluido desde el diseño que las contiene mediante el espacio de nombres de la app y el nombre de la variable en un atributo. En el siguiente ejemplo, se muestran las variables user incluidas de los archivos de diseño name.xml y 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 vinculación de datos no admite una inclusión como elemento secundario directo de un elemento de combinación. Por ejemplo, el siguiente diseño no es compatible:

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

Recursos adicionales

Para obtener más información sobre la vinculación de datos, consulta los siguientes recursos adicionales.

Ejemplos

Codelabs

Entradas de blog