Diseños y expresiones vinculantes

El lenguaje de expresiones permite escribir expresiones que manejan eventos enviados por las vistas. La biblioteca de vinculación de datos genera de forma automática las clases requeridas para vincular las vistas del diseño con los 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 al elemento raíz en un archivo de diseño no vinculante. En el siguiente código, se incluye 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 los data describe una propiedad que puede usarse 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 "@{}". Aquí, el texto de 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}" />
    

Objeto de datos

Supongamos por ahora que tienes un objeto estándar 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 aplicaciones, 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. Un ejemplo sería el uso de métodos de acceso en Java, como se muestra a continuación:

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} usada para el atributo android:text accederá al campo firstName en la primera clase y el método getFirstName() en la segunda. Como alternativa, 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. Lo que cambia es que se usan mayúsculas en la primera letra de cada palabra y se agrega el sufijo Binding. El nombre de archivo de diseño anterior es activity_main.xml, por lo que la clase 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. Se recomienda 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);
    }

    

En el tiempo de ejecución, la app muestra el usuario de prueba en la IU. Como alternativa, puedes obtener la vista usando 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 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 + - / * %
  • de concatenación de strings +
  • lógicos && ||
  • binarios & | ^
  • unarios + - ! ~
  • mayúscula >> >>> <<
  • de comparación == > < >= <= (ten en cuenta que < debe tener el formato de escape &lt;)
  • instanceof
  • agrupación ()
  • literales: caracteres, strings, números, null
  • de transmisión
  • de llamadas a métodos
  • de acceso de campo
  • de acceso de matriz []
  • operadores ternarios ?:

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 expresiones 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 elige el derecho si el primero es null.

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

Eso 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. Para ello, usa 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 el 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 mediante ID con la siguiente sintaxis:

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

En el siguiente ejemplo, la vista TextView hace referencia a una vista EditText del 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

Se puede acceder a colecciones comunes, como matrices, listas, listas dispersas y mapas, utilizando 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. Como se muestra en el ejemplo anterior, se puede reemplazar @{map[key]} 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 es posible utilizar comillas dobles para rodear el valor del atributo. En ese caso, los literales de string deben estar entre comillas `:

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 toma varios parámetros, debes pasar todos los parámetros:


      Have an orange
      Have %d oranges

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

Algunos recursos requieren una evaluación de tipo 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 manejo de expresiones que se envían desde las vistas (por ejemplo, el método onClick()). Los nombres de los atributos de 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(); por lo tanto, el atributo para este evento es android:onClick.

Hay algunos controladores de eventos especializados para el evento de clic que requieren un atributo distinto de android:onClick para evitar un conflicto. Puedes usar los siguientes atributos para evitar ese 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 los siguientes mecanismos para manejar 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 se evalúa una expresión como una referencia de método, la vinculación de datos une la referencia del método y el objeto propietario en un objeto de escucha y establece ese objeto de escucha en la vista de destino. Si se evalúa la expresión como null, la vinculación de datos no crea un objeto de escucha y, en cambio, establece un objeto de escucha null.
  • 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 despacha el evento, el objeto de escucha evalúa la expresión lambda.

Referencias de métodos

Los eventos pueden vincularse directamente a los métodos del controlador, de forma similar a como se puede asignar android:onClick a un método en una actividad. Una ventaja importante en comparación con el atributo onClick de View es que se procesa la expresión en el momento de la compilación, por lo que, si el método no existe o su firma es incorrecta, recibes un error de compilación.

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

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

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 permiten ejecutar expresiones de vinculación de datos arbitrarias. Esta función está disponible con el complemento de Gradle para 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 de resultado debe coincidir con el valor de resultado esperado del objeto de escucha (a menos que esté vacío). Por ejemplo, consideremos la siguiente clase de presentador que tiene el método onSaveClick():

Kotlin

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

    

Java

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

    

Luego, 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 utiliza 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 especificada. Como en las expresiones de vinculación regulares, obtienes igualmente seguridad nula y de subprocesos de la vinculación de datos mientras se evalúan estas expresiones de objetos de escucha.

En el ejemplo anterior, no definimos el parámetro de view que se pasa a onClick(View). Las vinculaciones de objetos de escucha proporcionan dos opciones para los parámetros de estos objetos: 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, la expresión anterior se podría escribir de la siguiente manera:

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

O bien, si quieres usar el parámetro en la expresión, podría funcionar 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)}"
    

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, las expresiones también deben mostrar el mismo tipo de valor. Por ejemplo, si deseas escuchar el evento de 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 no se puede evaluar la expresión debido a objetos null, la vinculación de datos muestra el valor predeterminado para ese tipo. Por ejemplo, null para tipos de referencia, 0 para int, false para boolean, etc.

Si necesitas usar una expresión con un predicado (por ejemplo, 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 muy eficientes y pueden hacer que el código sea muy fácil de leer. Por otro lado, los objetos de escucha que contienen expresiones complejas dificultan la lectura y el mantenimiento de los diseños. Estas expresiones deben ser tan sencillas como pasar los datos disponibles de tu IU a tu método de devolución de llamada. Debes implementar cualquier lógica empresarial dentro del método de devolución de llamada que invocaste 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 facilitan la referencia de clases dentro de los archivos de diseño. Las variables permiten describir una propiedad que puede usarse en expresiones de vinculación. Las inclusiones permiten reutilizar diseños complejos en tu app.

Importaciones

Las importaciones permiten hacer referencia fácilmente a clases dentro del archivo de diseño, al igual que en el código administrado. Se pueden usar cero o más elementos de import dentro del elemento de 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 permite hacer referencia a ella desde las 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, una de las clases puede renombrarse con un alias. En el siguiente ejemplo, se cambia el nombre de la clase View del paquete com.example.real.estate por Vista:

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

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

Se pueden usar los tipos importados como referencias de tipo en variables y expresiones. En el siguiente ejemplo, se muestra cómo usar User y List 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>
    

También 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"/>
    

Los tipos importados también se pueden usar cuando se hace referencia a campos y métodos estáticos en 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 establecer 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, de modo 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), se combinan las variables. 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.

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. Se anula la variable de context mediante una declaración de variable explícita con ese nombre.

Inclusiones

Se pueden pasar las 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 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 inclusiones 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