Ya está disponible la segunda Vista previa para desarrolladores de Android 11; pruébala y comparte tus comentarios.

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} utilizada para el atributo android:text accede al campo firstName en la clase anterior y al método getFirstName() en la clase posterior. 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. Alternativamente, 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 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 + - / * %
  • de concatenación de strings +
  • lógicos && ||
  • binarios & | ^
  • unarios + - ! ~
  • de desplazamiento >> >>> <<
  • de comparación == > < >= <= (ten en cuenta que < debe tener el formato de escape &lt;)
  • instanceof
  • de agrupación ()
  • literales (caracteres, strings, numéricos, 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 (??) selecciona el operando izquierdo si no es null o el derecho si el anterior es null.

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

Eso es funcionalmente equivalente a lo siguiente:

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

Referencia de propiedad

Una expresión puede hacer referencia a una propiedad en una clase. Para ello, usa el siguiente formato, que es el mismo para campos, captadores 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, user.name tiene asignado 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.

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. Por ejemplo, @{map[key]} en el ejemplo anterior se puede reemplazar con @{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

Puedes acceder a los recursos en una expresión utilizando la siguiente sintaxis:

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

Los plurales y strings de formato se pueden evaluar. Para ello, proporciona parámetros:

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

Cuando un plural toma varios parámetros, se deben 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 que el atributo para ese 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 Establecedor 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 una expresión se evalúa como una referencia de método, la vinculación de datos envuelve 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 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 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 View de onClick es que la expresión se procesa 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 objetos de escucha: 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 la expresión no se puede evaluar 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 en el paquete com.example.real.estate a 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.Se puede usar 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 muestran User y List usados 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. El siguiente código importa la clase MyStringUtils y hace referencia a su método de 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, java.lang.* se importa automáticamente.

Variables

Puedes usar múltiples elementos variable dentro del elemento de 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 momento de compilación, por lo que, si una variable implementa Observable o es una colección observable, eso debería 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 establecedor y un captador para cada una de las variables descritas. Las variables toman los valores de código administrados predeterminados hasta que se llama al establecedor: 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 del context es el objeto Context del método getContext() de la vista raíz. La variable de context se anula mediante una declaración de variable explícita con ese nombre.

Inclusiones

Las variables se pueden pasar 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 variables de 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 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.

Muestras

Codelabs

Entradas de blog (en inglés)