Skip to content

Most visited

Recently visited

navigation

Biblioteca de vinculación de datos

En este documento se explica la manera de usar la biblioteca de vinculación de datos para escribir diseños declarativos y minimizar el código de adhesión necesario para vincular la lógica y los diseños de tu aplicación.

La biblioteca de vinculación de datos ofrece flexibilidad y amplia compatibilidad. Es una biblioteca de compatibilidad, de modo que puedes usarla con todas las versiones de la plataforma Android hasta Android 2.1 (a partir del nivel de API 7).

Para usar la vinculación de datos, se necesita el complemento de Android para Gradle 1.5.0-alpha1 o una versión posterior.

Entorno de compilación

Para comenzar con la vinculación de datos, descarga la biblioteca desde el repositorio de compatibilidad de Android SDK Manager.

Para configurar el uso de vinculación de datos en tu app, agrega el elemento dataBinding a tu archivo build.gradle en el módulo de esta.

Usa el siguiente fragmento de código para configurar la vinculación de datos:

android {
    ....
    dataBinding {
        enabled = true
    }
}

Si hay un módulo de app que depende de una biblioteca que usa vinculación de datos, también debe configurarse la vinculación de datos en el archivo build.gradle de este módulo.

A su vez, asegúrate de usar una versión compatible de Android Studio. Android Studio 1.3 y las versiones posteriores proporcionan compatibilidad con la vinculación de datos como se describe en Compatibilidad de Android Studio con la vinculación de datos.

Archivos de diseño de vinculación de datos

Cómo escribir tu primer conjunto de expresiones de vinculación de datos

Los archivos de diseño de vinculación de datos son un poco diferentes y comienzan con una etiqueta raíz layout seguida de un elemento data y un elemento raíz view. Este elemento view representa lo que sería tu raíz en un archivo de diseño sin vinculación. Un archivo de ejemplo tiene el siguiente aspecto:

<?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 de usuario dentro de datos describe una propiedad que se puede usar dentro de este diseño.

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

Las expresiones dentro del diseño se escriben en las propiedades del atributo usando la sintaxis “@{}”. Aquí, el texto de TextView se fija en la propiedad del usuario firstName:

<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 Java (POJO) antiguo para el usuario:

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. Es común que las aplicaciones tengan datos que se lean una vez y nunca se vuelvan a cambiar. También es posible usar objetos JavaBeans:

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, estas dos clases son equivalentes. La expresión @{user.firstName} usada para el atributo de TextView android:text accederá al campo firstName en la primera clase y el método getFirstName() en la segunda. De manera alternativa, también se resolverá a firstName() si ese método existe.

Vinculación de datos

De forma predeterminada, se generará una clase Binding según el nombre del archivo de diseño; se aplicará una conversión a caso Pascal y se agregará el sufijo “Binding”. El archivo de diseño anterior llevaba el nombre main_activity.xml. Por lo tanto, la clase generada se denominó MainActivityBinding. Esta clase contiene todas las vinculaciones de las propiedades de diseño (p. ej., la variable user) con los elementos View del diseño y determina la manera de asignar valores para las expresiones de vinculación. La manera más sencilla de crear las vinculaciones es proceder durante la expansión:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

¡Eso es todo! Ejecuta la aplicación. Verás “Test User” en la IU. De manera alternativa, puedes obtener la vista con lo siguiente:

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

Si usas los elementos de vinculación de datos dentro de un adaptador ListView o RecyclerView, tal vez prefieras usar lo siguiente:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

Manejo de eventos

La vinculación de datos te permite escribir expresiones que manejan eventos enviados desde las vistas (p. ej., onClick). Los nombres de atributos de eventos se rigen por el nombre del método de receptor, con algunas excepciones. Por ejemplo, View.OnLongClickListener tiene un método onLongClick(). Por lo tanto, el atributo para este evento es android:onLongClick. Hay dos maneras de manejar un evento.

Referencias de métodos

Los eventos se pueden vincular directamente con métodos de control, procedimiento similar a la alternativa por la cual se puede asignar android:onClick a un método en una actividad. Una de las mayores ventajas en comparación con el atributo View#onClick es que la expresión se procesa en el momento de la compilación, de modo que si el método no existe o su firma no es correcta, observarás un error en el momento de compilación.

La mayor diferencia entre referencias de métodos y vinculaciones de receptor es que la implementación del receptor real se crea cuando los datos se vinculan, no cuando el evento se activa. Si prefieres evaluar la expresión cuando tiene lugar el evento, debes usar la vinculación de receptores.

Para asignar un evento a su controlador, usa una expresión de vinculación normal, con el valor que representa el nombre de método que se llamará. Por ejemplo, si tu objeto de datos tiene dos métodos:

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

La expresión de vinculación puede asignar el receptor de clics para un elemento View:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <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>

Ten en cuenta que la firma del método de la expresión debe coincidir exactamente con la firma del método del objeto receptor.

Vinculaciones de receptores

Las vinculaciones de receptores son expresiones de vinculación que se ejecutan cuando tiene lugar un evento. Son similares a las referencias de métodos, pero te permiten ejecutar expresiones de vinculación de datos arbitrarios. Esta función se encuentra disponible a partir de la versión 2.0 del complemento de Android para Gradle.

En las referencias de métodos, los parámetros del método deben coincidir con los parámetros del receptor de eventos. En vinculaciones de receptores, solo tu valor de retorno debe coincidir con el valor de retorno del receptor que se espera (a menos que se espere uno vacío). Por ejemplo, puedes tener una clase presentadora que tenga el siguiente método:

public class Presenter {
    public void onSaveClick(Task task){}
}
Luego puedes vincular el evento de clic con tu clase 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>

Los receptores están representados por expresiones Lambda que solo están permitidas como elementos de raíz de tus expresiones. Cuando se usa un callback en una expresión, la vinculación de datos genera de manera automática el receptor necesario y se registra para el evento. Cuando la vista activa el evento, la vinculación de datos evalúa la expresión dada. Al igual que en las expresiones de vinculación regulares, se obtiene de todas formas la seguridad de tipo “null” y de subprocesos de vinculación de datos mientras se evalúan estas expresiones de receptor.

Ten en cuenta que en el ejemplo anterior no definimos el parámetro view que se pasa a onClick(android.view.View). Las vinculaciones de receptores proporcionan dos opciones para los parámetros de receptor: puedes ignorar todos los parámetros del método u otorgarles un nombre a todos ellos. Si prefieres nombrar los parámetros, puedes usarlos en tu expresión. Por ejemplo, la expresión anterior podría escribirse de esta manera:

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"
Como alternativa, si deseas usar el parámetro en la expresión, podría funcionar de la siguiente manera:
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:
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 recibes muestra un valor cuyo tipo no es void, tus expresiones también deben mostrar el mismo tipo de valor. Por ejemplo, si deseas recibir el evento de clic largo, tu expresión debe mostrar boolean.

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 de Java predeterminado para ese tipo. Por ejemplo, null para tipos de referencia, 0 para int, false para boolean, etc.

Si debes usar una expresión con un predicado (p. ej., ternario), puedes usar void como un símbolo.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Evitar receptores complejos
Las expresiones de receptor son muy poderosas y pueden facilitar mucho la lectura de tu código. Por otra parte, los receptores que tienen expresiones complejas dificultan la lectura de tus diseños e imposibilitan su mantenimiento. Estas expresiones deben ser tan sencillas como el paso de datos disponibles de tu IU a tu método callback. Debes implementar cualquier lógica de negocios dentro del método callback que invocaste desde la expresión de receptor.

Existen algunos controladores de eventos de clic especializados y necesitan un atributo que no sea android:onClick para evitar un conflicto. Se han creado los siguientes atributos para evitar conflictos:

Clase Establecedor de receptores Atributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Detalles de diseño

Importaciones

Dentro del elemento data es posible usar varios elementos import o que no se use ninguno. Estos permiten hacer una referencia sencilla a clases dentro de tu archivo de diseño, al igual que en Java.

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

Ahora, se puede usar el elemento View dentro de tu expresión de vinculación:

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

Cuando existen conflictos en los nombres de clases, se puede modificar el de una de estas puede con un “alias”:

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

Vista se puede usar para hacer referencia a com.example.real.estate.View y View se puede usar para hacer referencia a android.view.View dentro del archivo de diseño. Los tipos importados pueden usarse como referencias de tipos en variables y expresiones:

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

Nota: En Android Studio aún no se manejan importaciones. Por lo tanto, es posible que la función de autocompletar para variables importadas no funcione en tu IDE. De todos modos, tu aplicación podrá compilarse bien y podrás corregir el error de IDE usando nombres totalmente calificados en tus definiciones de variables.

<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 al hacer referencia a campos y métodos estáticos en las expresiones:

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

Como en Java, java.lang.* se importa de manera automática.

Variables

Cualquier número de elementos variable se puede usar dentro del elemento data. En cada elemento variable se describe una propiedad que puede establecerse en el diseño que se usará en expresiones de vinculación, dentro del archivo de diseño.

<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, 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 clase base o interfaz que no implementa la interfaz Observable*, las variables no se observarán.

Cuando hay diferentes archivos de diseño para varias configuraciones (p. ej., horizontal o vertical), se combinarán las variables. No debe haber definiciones variables conflictivas entre estos archivos de diseño.

La clase Binding generada tendrá un establecedor y un captador para cada una de las variables descritas. Las variables tomarán los valores Java predeterminados hasta que el establecedor se llame — null para tipos de referencia, 0 para int, false para boolean, etc.

Una variable especial llamada context se genera para usarse en expresiones de vinculación según sea necesario. El valor para context es Context del elemento getContext() del elemento raíz View. La variable context se reemplazará por una declaración de la variable explícita que lleve ese nombre.

Nombres de clases de vinculación personalizada

De forma predeterminada, una clase Binding se genera según el nombre del archivo de diseño; se aplican mayúsculas al principio, se eliminan los guiones bajos ( _ ), se aplican mayúsculas a las letras que siguen a estos y se agrega el sufijo “Binding”. Esta clase se ubicará en un paquete de vinculación de datos bajo el paquete del módulo. Por ejemplo, a partir del archivo de diseño contact_item.xml se generará ContactItemBinding. Si el paquete del módulo es com.example.my.app, este luego se dispondrá en com.example.my.app.databinding.

Las clases Binding se pueden volver a nombrar o se pueden disponer en paquetes diferentes ajustando el atributo class del elemento data. Por ejemplo:

<data class="ContactItem">
    ...
</data>

Esto genera la clase Binding como ContactItem en el paquete de vinculación de datos del paquete del módulo. Si la clase debe generarse en un paquete diferente dentro del paquete del módulo, puede contener un prefijo “.”:

<data class=".ContactItem">
    ...
</data>

En este caso, ContactItem se genera en el paquete del módulo directamente. Se puede usar cualquier paquete si se proporciona el paquete completo:

<data class="com.example.ContactItem">
    ...
</data>

Parámetros include

Se pueden pasar variables a la vinculación de un diseño incluido desde el diseño contenedor usando el espacio de nombres de la aplicación y el nombre de la variable de un atributo:

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

Aquí, debe haber una variable user en los archivos de diseño name.xml y contact.xml.

La vinculación de datos no admite el parámetro include como un campo secundario directo de un elemento merge. 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>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

Lenguaje de expresión

Funciones comunes

El lenguaje de expresión es muy similar a una expresión Java. Estos son los mismos:

Ejemplos:

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

Operaciones faltantes

Faltan algunas operaciones de la sintaxis de expresión que puedes usar en Java.

Operador de incorporación nulo

El operador de incorporación nulo (??) elige el operando izquierdo si no es nulo o el derecho si es nulo.

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

Esto es funcionalmente equivalente a:

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

Referencia de propiedades

La primera ya se analizó en Cómo escribir tus primeras expresiones de vinculación de datos: referencias de JavaBean de forma corta. Cuando una expresión hace referencia a una propiedad de una clase, usa el mismo formato para campos, captadores y objetos ObservableField.

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

Evitar NullPointerException

El código de vinculación de datos generado verifica de manera automática los elementos null y evita las excepciones de puntero nulo. Por ejemplo, en la expresión @{user.name}, si user es null, a user.name se le asignará su valor predeterminado (null). Al hacer referencia a user.age, donde “age” es un int, se fijaba de forma predeterminada en 0.

Colecciones

Colecciones comunes: por cuestiones de conveniencia, se puede acceder a matrices, listas, listas no precisas y mapas usando el operador [].

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

Literales de strings

Cuando se usan comillas simples en el valor de atributo, usar comillas dobles en la expresión es sencillo:

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

También es posible usar comillas dobles para encerrar el valor de atributo. Cuando se hace eso, en los literales de strings deben usarse ' o la comilla inversa (`).

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

Recursos

Es posible ingresar recursos como parte de las expresiones que usan la sintaxis normal:

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

Las strings de formato y los plurales se pueden evaluar proporcionando parámetros:

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

Cuando un plural toma varios parámetros, todos ellos se deben pasar:


  Have an orange
  Have %d oranges

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

Algunos recursos requieren una evaluación de tipos explícita.

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

Objetos de datos

Cualquier objeto Java antiguos sin formato (POJO) se puede usar para la vinculación de datos, pero la modificación de un POJO no hará que la IU se actualice. El poder real de la vinculación de datos se puede usar al proporcionar a tus objetos de datos la capacidad de emitir notificaciones cuando cambian los datos. Existen tres mecanismos diferentes de notificación de cambios de datos: los Objetos observables, los campos observables y las colecciones observables.

Cuando uno de estos objetos de datos observables dependa de la IU y una propiedad de objetos de datos cambie, la IU se actualizará de forma automática.

Objetos observables

Una clase que implemente la interfaz Observable permitirá que la vinculación adjunte un único receptor a un objeto vinculado para recibir los cambios de todas las propiedades en ese objeto.

La interfaz Observable tiene un mecanismo para agregar y eliminar receptores, pero la notificación depende del desarrollador. A fin de facilitar el desarrollo, se creó la clase base BaseObservable para implementar el mecanismo de registro de receptores. El implementador de clases de datos es aún responsable de enviar notificaciones cuando cambian las propiedades. Esto se realiza asignando una anotación Bindable al captador y realizando una notificación en el receptor.

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

La anotación Bindable genera una entrada en el archivo de la clase BR durante la compilación. El archivo de clase BR se generará en el paquete del módulo. Si la clase base para clases de datos no se puede cambiar, se puede implementar la interfaz Observable usando el PropertyChangeRegistry apropiado para almacenar y notificar receptores de modo eficiente.

Objetos ObservableField

Crear clases Observable implica un poco de trabajo; por ello, los desarrolladores que deseen ahorrar tiempo o tengan pocas propiedades podrán usar ObservableField y sus elementos del mismo nivel: ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble y ObservableParcelable. Los ObservableFields son objetos observables independientes que tienen un único campo. Las versiones primitivas evitan la conversión boxing y unboxing durante operaciones de acceso. Para usarlas, crea un campo final público en la clase de datos:

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

¡Eso es todo! Para ingresar el valor, usa los métodos de descriptor de acceso set y get:

user.firstName.set("Google");
int age = user.age.get();

Colecciones observables

Algunas aplicaciones usan estructuras más dinámicas para contener datos. Las colecciones observables permiten el acceso con clave a estos objetos de datos. ObservableArrayMap es útil cuando la clave es un tipo de referencia, como String.

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

En el diseño, se puede acceder al mapa a través de las claves String:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

ObservableArrayList es útil cuando la clave es un número entero:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

En el diseño, se puede acceder a la lista a través de los índices:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Vinculación generada

La clase Binding generada vincula las variables de diseño con los elementos View dentro del diseño. Según lo discutido anteriormente, el nombre y el paquete de la vinculación pueden ser personalizados. Todas las clases de vinculación generadas extienden ViewDataBinding.

Creación

La vinculación debe crearse poco después del aumento para garantizar que la jerarquía de elementos View no se vea interrumpida antes de la vinculación con las vistas mediante expresiones dentro del diseño. Hay algunos modos de realizar vinculaciones con un diseño. El más común consiste en usar los métodos estáticos en la clase Binding. El método inflate aumenta la jerarquía de elementos View y se vincula a ella en un paso. Hay una versión más simple que solo toma una clase LayoutInflater y uno que toma una clase ViewGroup también:

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

Si se aumentó el diseño con un mecanismo diferente, es posible vincularlo por separado:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

A veces, la vinculación no se puede conocer por adelantado. En esos casos, se puede crear la vinculación usando la clase DataBindingUtil:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

Vistas con ID

Se generará un campo final público para cada elemento View con un ID en el diseño. La compilación realiza un único paso en la jerarquía View, con lo cual se extraen los elementos View con los ID. Este mecanismo puede ser más rápido que llamar a findViewById para varios elementos View. Por ejemplo:

<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}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

Generará una clase Binding con:

public final TextView firstName;
public final TextView lastName;

Los ID no son en absoluto tan necesarios como ante la ausencia de vinculación de datos, pero hay de todos modos algunas instancias en las cuales es necesario acceder a los elementos View desde el código.

Variables

A cada variable se le otorgarán métodos de descriptores de acceso.

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

Se generarán establecedores y captadores en la vinculación:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

ViewStubs

Los ViewStub se diferencian un tanto de los elementos Views normales. Comienzan sin ser visibles y cuando se vuelven visibles, o reciben indicaciones explícitas para aumentar, se reemplazan en el diseño aumentando otro diseño.

Debido a que ViewStub en esencia desaparece de la jerarquía de elementos View, el elemento View del objeto de vinculación también debe desaparecer para permitir la colección. Debido a que los elementos View son definitivos, un objeto ViewStubProxy toma el lugar de ViewStub, lo cual otorga al desarrollador acceso a ViewStub cuando existe y también acceso a la jerarquía aumentada de elementos View cuando se aumenta ViewStub.

Cuando se aumenta otro diseño, se debe establecer una vinculación para el diseño nuevo. Por lo tanto, ViewStubProxy debe recibir el elemento ViewStub.OnInflateListener de ViewStub y establecer la vinculación en ese momento. Ya que solo uno puede existir, ViewStubProxy permite al desarrollador establecer en este un elemento OnInflateListener que llamará después de establecer la vinculación.

Vinculación avanzada

Variables dinámicas

A veces, la clase Binding específica no será conocida. Por ejemplo, un elemento RecyclerView.Adapter que funciona con diseños arbitrarios no reconocerá la clase Binding específica. Debe, de todos modos, asignar el valor Binding durante onBindViewHolder(VH, int).

En este ejemplo, todos los diseños que RecyclerView vincula tienen una variable “item”. BindingHolder tiene un método getBinding que muestra la base ViewDataBinding.

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

Vinculación inmediata

Cuando una variable o un objeto observable cambien, la vinculación se programará para cambiar antes del siguiente fotograma. Sin embargo, hay momentos en los cuales la vinculación debe ejecutarse de inmediato. Para forzar la ejecución, usa el método executePendingBindings().

Subproceso en segundo plano

Puedes cambiar tu modelo de datos en un subproceso en segundo plano mientras no sea una colección. La vinculación de datos localizará cada variable o campo durante la evaluación para evitar cualquier problema de concurrencia.

Establecedores de atributos

Cada vez que un valor vinculado cambia, la clase Binding generada debe llamar a un método establecedor en el elemento View con la expresión de vinculación. El framework de vinculación de datos tiene maneras de personalizar el método que se llamará para establecer el valor.

Establecedores automáticos

Para un atributo, la vinculación de datos intenta encontrar el método setAttribute. El espacio de nombres para el atributo no tiene importancia; solo la tiene el nombre del atributo.

Por ejemplo, una expresión asociada con el atributo android:text de TextView buscará un elemento setText(String). Si la expresión muestra un int, la vinculación de datos buscará un método setText(int). Procura que la expresión muestre el tipo correcto. Si es necesario, realiza una transmisión. Ten en cuenta que la vinculación de datos funcionará incluso si no existe un atributo con el nombre en cuestión. Luego, podrás “crear” de manera sencilla atributos para cualquier establecedor usando la vinculación de datos. Por ejemplo, el elemento DrawerLayout de respaldo no tiene atributos, pero tiene muchos establecedores. Puedes usar los establecedores automáticos para emplear uno de estos.

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

Establecedores con nombres restablecidos

Algunos atributos tienen establecedores que no coinciden por nombre. Para estos métodos, se puede asociar un atributo con el establecedor a través de la anotación BindingMethods. Esto se debe asociar con una clase y contiene anotaciones de BindingMethod, una por cada método con nombre restablecido. Por ejemplo, el atributo android:tint se asocia en realidad con setImageTintList(ColorStateList), no con setTint.

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

Es poco probable que los desarrolladores necesiten volver a nombrar establecedores; los atributos del framework de Android ya se han implementado.

Establecedores personalizados

Algunos atributos necesitan lógica de vinculación personalizada. Por ejemplo, no hay un establecedores asociados para el atributo android:paddingLeft. Como alternativa, existe setPadding(left, top, right, bottom). Un método de adaptador de vinculación estático con la anotación BindingAdapter permite al desarrollador personalizar la manera en que se llama a un establecedor para un atributo.

Ya se han creado BindingAdapter para los atributos de Android. Por ejemplo, a continuación se muestra uno para paddingLeft:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

Los adaptadores de vinculación son útiles para otros tipos de personalización. Por ejemplo, un cargador personalizado se puede llamar fuera del subproceso para cargar una imagen.

Los adaptadores de vinculación creados por el desarrollador anularán los adaptadores de vinculación de datos predeterminados cuando haya un conflicto.

También puedes tener adaptadores que reciban varios parámetros.

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

Se llamará a este adaptador si imageUrl y error se usan para un elemento ImageView, imageUrl es una string y error es un elemento de diseño.

La vinculación de métodos de adaptadores puede opcionalmente tomar los valores viejos para sus controladores. Un método que toma valores anteriores y nuevos debe tener todos los valores anteriores para que los atributos aparezcan primeros seguidos de los valores nuevos:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

Los controladores de eventos solo pueden usarse con interfaces o con clases abstractas con un método abstracto. Por ejemplo:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

Cuando un receptor tiene varios métodos, se debe dividir en varios receptores. Por ejemplo, View.OnAttachStateChangeListener tiene dos métodos: onViewAttachedToWindow() y onViewDetachedFromWindow(). Se deben crear dos interfaces para diferenciar los atributos y los controladores para ellos.

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

Debido a que el cambio de un receptor también afectará al otro, es necesario tener tres adaptadores de vinculación diferentes, uno para cada atributo y uno para ambos, si ambos se configuran.

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

El ejemplo anterior es un poco más complicado de lo normal porque el elemento View usa add o remove para el receptor en lugar de un método establecido para View.OnAttachStateChangeListener. La clase android.databinding.adapters.ListenerUtil ayuda a realizar un seguimiento de los receptores previos para que se puedan eliminar en el adaptador de vinculación.

Mediante la anotación de las interfaces OnViewDetachedFromWindow y OnViewAttachedToWindow con @TargetApi(VERSION_CODES.HONEYCOMB_MR1), el generador de códigos de vinculación de datos determina que el receptor solo se debe generar cuando se ejecuta en Honeycomb MR1 y en dispositivos nuevos, la misma versión admitida por addOnAttachStateChangeListener(View.OnAttachStateChangeListener).

Convertidores

Conversiones de objetos

Cuando se muestre un objeto de una expresión de vinculación, se elegirá un establecedor de los establecedores automáticos, con nombre restablecido y personalizados. El objeto se transmitirá a un tipo de parámetro del establecedor elegido.

Esto resulta conveniente para aquellos que usan ObservableMaps para contener datos. Por ejemplo:

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

userMap muestra un objeto y ese objeto se transmite de forma automática a un tipo de parámetro en el setText(CharSequence) del establecedor. Cuando pueda surgir alguna confusión con respecto al tipo de parámetro, el desarrollador deberá transmitir la expresión.

Conversión personalizada

A veces las conversiones deben ser automáticas entre tipos específicos. Por ejemplo, al establecer el fondo:

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

Aquí, el fondo toma un elemento Drawable, pero el color es un número entero. Cada vez que se espera un Drawable y se muestra un número entero, el int se debe convertir en ColorDrawable. Esta conversión se realiza usando un método estático con una anotación BindingConversion:

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

Ten en cuenta que las conversiones solo suceden en el nivel establecedor, de modo que no está permitido combinar tipos como este:

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

Compatibilidad de Android Studio con la vinculación de datos

Android Studio admite varias funciones de edición de códigos para código de vinculación de datos. Por ejemplo, admite las siguientes funciones para expresiones de vinculación de datos:

Nota: Las matrices y un tipo genérico, como la clase Observable, pueden mostrar errores donde no existen.

En el panel Preview se muestran valores predeterminados para expresiones de vinculación de datos si se proporcionan. En el siguiente ejemplo de extracto de un elemento de un archivo XML de diseño, en el panel Preview se muestra el valor de texto predeterminado PLACEHOLDER en TextView.

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

Si debes mostrar un valor predeterminado durante la etapa de diseño de tu proyecto, también puedes usar los atributos de herramientas en lugar de valores de expresión predeterminados, como se describe en Atributos de diseño de tiempo de diseño.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)