El lenguaje de expresiones te permite escribir expresiones que controlan eventos enviados por vistas. La biblioteca de vinculación de datos genera automáticamente las clases necesarias para vincular las vistas del diseño con tus objetos de datos.
Los archivos de diseño de la vinculación de datos son ligeramente diferentes y comienzan con una etiqueta raíz de layout
, seguida de un elemento de data
y un elemento raíz de view
. Este elemento de vista corresponde a la raíz en un archivo de diseño no vinculante. En el siguiente código, se muestra un archivo de diseño de muestra:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
La variable user
dentro de data
describe una propiedad que se puede usar en este diseño:
<variable name="user" type="com.example.User" />
Las expresiones del diseño se escriben en las propiedades del atributo con la sintaxis @{}
. En el siguiente ejemplo, el texto TextView
se establece en la propiedad firstName
de la variable user
:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
Objetos de datos
Supongamos que tienes un objeto sin formato para describir la entidad User
:
Kotlin
data class User(val firstName: String, val lastName: String)
Java
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
Este tipo de objeto tiene datos que nunca cambian. En las apps, es común tener datos que se leen una vez y nunca cambian. También es posible usar un objeto que siga un conjunto de convenciones, como el uso de métodos de acceso en el lenguaje de programación Java, como se muestra en el siguiente ejemplo:
Kotlin
// Not applicable in Kotlin. data class User(val firstName: String, val lastName: String)
Java
public class User { private final String firstName; private final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } }
Desde la perspectiva de la vinculación de datos, esas dos clases son equivalentes. La expresión @{user.firstName}
que se usa para el atributo android:text
accede al campo firstName
en la primera clase y al método getFirstName()
en la segunda. También se resuelve en firstName()
, si ese método existe.
Cómo vincular datos
Para cada archivo de diseño, se genera una clase de vinculación. De forma predeterminada, el nombre de la clase se basa en el nombre del archivo de diseño, convertido en mayúsculas y minúsculas, con el sufijo Binding. Por ejemplo, el nombre de archivo de diseño anterior es activity_main.xml
, por lo que la clase de vinculación generada correspondiente es ActivityMainBinding
.
Esta clase contiene todas las vinculaciones, desde las propiedades de diseño (por ejemplo, la variable user
) hasta las vistas del diseño, y sabe cómo asignar valores para las expresiones de vinculación. Te recomendamos crear las vinculaciones mientras se aumenta el diseño, como se muestra en el siguiente ejemplo:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.user = User("Test", "User") }
Java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Test", "User"); binding.setUser(user); }
Durante el tiempo de ejecución, la app muestra el usuario de prueba en la IU. Como alternativa, puedes obtener la vista utilizando un LayoutInflater
, como se muestra en el siguiente ejemplo:
Kotlin
val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
Java
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
Si usas elementos de vinculación de datos dentro de un adaptador Fragment
, ListView
o RecyclerView
, es posible que prefieras usar los métodos inflate()
de las clases de vinculación o la clase DataBindingUtil
, como se muestra en el siguiente ejemplo de código:
Kotlin
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false) // or val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
Java
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); // or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Lenguaje de expresiones
Funciones comunes
El lenguaje de expresiones se parece mucho a las expresiones que se encuentran en el código administrado. Puedes usar los siguientes operadores y palabras clave en el lenguaje de expresiones:
- Matemáticos:
+ - / * %
- Concatenación de cadenas:
+
- Lógico:
&& ||
- Binario:
& | ^
- Unario:
+ - ! ~
- Mayúsculas:
>> >>> <<
- Comparación:
== > < >= <=
(<
debe tener el formato de escape<
) instanceof
- Agrupamiento:
()
- Literales, como caracteres, strings, números o
null
- Transmisión
- de llamadas a métodos
- de acceso de campo
- Acceso al array:
[]
- Operador ternario:
?:
Estos son algunos ejemplos:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
Operaciones faltantes
Faltan las siguientes operaciones de la sintaxis de expresión que puedes usar en el código administrado:
this
super
new
- invocación genérica explícita
Operador coalescente nulo
El operador coalescente nulo (??
) elige el operando izquierdo si no es null
o el derecho si el primero es null
:
android:text="@{user.displayName ?? user.lastName}"
Esto es funcionalmente equivalente a lo siguiente:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Referencias de propiedades
Una expresión puede hacer referencia a una propiedad en una clase mediante el siguiente formato, que es el mismo para campos, métodos get y objetos ObservableField
:
android:text="@{user.lastName}"
Cómo evitar excepciones de puntero nulo
El código de vinculación de datos generado verifica automáticamente si hay valores null
y evita las excepciones de puntero nulo. Por ejemplo, en la expresión @{user.name}
, si user
es nulo, a user.name
se le asigna su valor predeterminado de null
. Si haces referencia a user.age
, donde la edad es de tipo int
, la vinculación de datos usa el valor predeterminado de 0
.
Referencias de vistas
Una expresión puede hacer referencia a otras vistas en el diseño por ID con la siguiente sintaxis:
android:text="@{exampleText.text}"
En el siguiente ejemplo, la vista TextView
hace referencia a una vista EditText
en el mismo diseño:
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
Colecciones
Puedes acceder a colecciones comunes, como arrays, listas, listas dispersas y mapas, con el operador []
para mayor comodidad.
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"
También puedes hacer referencia a un valor en el mapa con la notación object.key
. Por ejemplo, puedes reemplazar @{map[key]}
en el ejemplo anterior por @{map.key}
.
Literales de string
Puedes usar comillas simples para rodear el valor del atributo, lo que te permite usar comillas dobles en la expresión, como se muestra en el siguiente ejemplo:
android:text='@{map["firstName"]}'
También puedes usar comillas dobles para rodear el valor del atributo. Cuando lo hagas, los literales de string deben estar rodeados de acentos graves `
, como se muestra a continuación:
android:text="@{map[`firstName`]}"
Recursos
Una expresión puede hacer referencia a los recursos de la app con la siguiente sintaxis:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
Puedes evaluar las strings de formato y los valores plurales proporcionando parámetros:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
Puedes pasar referencias de propiedades y referencias de vistas como parámetros de recursos:
android:text="@{@string/example_resource(user.lastName, exampleText.text)}"
Cuando un plural tome varios parámetros, pasa todos los parámetros:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
Algunos recursos requieren una evaluación de tipos explícito, como se muestra en la siguiente tabla:
Tipo | Referencia normal | Referencia de expresiones |
---|---|---|
String[] |
@array |
@stringArray |
int[] |
@array |
@intArray |
TypedArray |
@array |
@typedArray |
Animator |
@animator |
@animator |
StateListAnimator |
@animator |
@stateListAnimator |
color int |
@color |
@color |
ColorStateList |
@color |
@colorStateList |
Manejo de eventos
La vinculación de datos te permite escribir eventos de control de expresiones que se despachan desde las vistas, por ejemplo, el método onClick()
. Los nombres de los atributos de los eventos están determinados por el nombre del método del objeto de escucha,
con algunas excepciones. Por ejemplo, View.OnClickListener
tiene un método onClick()
, de manera que el atributo para este evento es android:onClick
.
Hay algunos controladores de eventos especializados para el evento de clic que necesitan un atributo distinto de android:onClick
para evitar un conflicto. Puedes usar los siguientes atributos para evitar este tipo de conflictos:
Clase | Método set de objetos de escucha | Atributo |
---|---|---|
SearchView |
setOnSearchClickListener(View.OnClickListener) |
android:onSearchClick |
ZoomControls |
setOnZoomInClickListener(View.OnClickListener) |
android:onZoomIn |
ZoomControls |
setOnZoomOutClickListener(View.OnClickListener) |
android:onZoomOut |
Puedes usar estos dos mecanismos, descritos en detalle en las siguientes secciones, para controlar un evento:
- Referencias de métodos: En tus expresiones, puedes hacer referencia a métodos que se ajusten a la firma del método del objeto de escucha. Cuando una expresión se evalúa como una referencia de método, la vinculación de datos une la referencia del método y el objeto de propietario en un objeto de escucha y establece ese objeto de escucha en la vista de destino. Si la expresión se evalúa como
null
, la vinculación de datos no crea un objeto de escucha y establece un objeto de escuchanull
en su lugar. - Vinculaciones de objetos de escucha: Son expresiones lambda que se evalúan cuando ocurre el evento. La vinculación de datos siempre crea un objeto de escucha, que se establece en la vista. Cuando se envía el evento, el objeto de escucha evalúa la expresión lambda.
Referencias de métodos
Puedes vincular eventos a los métodos del controlador directamente, de manera similar a como se asigna android:onClick
a un método en una actividad. Una ventaja en comparación con el atributo onClick
de View
es que la expresión se procesa en el tiempo de compilación. Por lo tanto, si el método no existe o su firma es incorrecta, recibirás un error de tiempo de compilación.
La principal diferencia entre las referencias de métodos y las vinculaciones de objetos de escucha es que la implementación real del objeto de escucha se crea cuando se vinculan los datos, no cuando se activa el evento. Si prefieres evaluar la expresión cuando ocurra el evento, usa las vinculaciones de objetos de escucha.
Para asignar un evento a su controlador, usa una expresión de vinculación normal; el valor debe ser el nombre del método que se llamará. Por ejemplo, considera el siguiente objeto de datos de diseño de ejemplo:
Kotlin
class MyHandlers { fun onClickFriend(view: View) { ... } }
Java
public class MyHandlers { public void onClickFriend(View view) { ... } }
La expresión de vinculación puede asignar el objeto de escucha de clics de una vista al método onClickFriend()
de la siguiente manera:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
Vinculaciones de objetos de escucha
Las vinculaciones de los objetos de escucha son expresiones de vinculación que se ejecutan cuando ocurre un evento. Son similares a las referencias de métodos, pero te permiten ejecutar expresiones de vinculación de datos arbitrarias. Esta función está disponible con el complemento de Android para Gradle 2.0 y versiones posteriores.
En las referencias de métodos, los parámetros del método deben coincidir con los parámetros del objeto de escucha de eventos. En las vinculaciones de objetos de escucha, solo el valor que se muestra debe coincidir con el valor que se muestra esperado del objeto de escucha, a menos que esté esperando void
. Por ejemplo, considera la siguiente clase de presentador que tiene un método onSaveClick()
:
Kotlin
class Presenter { fun onSaveClick(task: Task){} }
Java
public class Presenter { public void onSaveClick(Task task){} }
Puedes vincular el evento de clic al método onSaveClick()
de la siguiente manera:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
Cuando se usa una devolución de llamada en una expresión, la vinculación de datos crea automáticamente el objeto de escucha necesario y lo registra para el evento. Cuando la vista activa el evento, la vinculación de datos evalúa la expresión dada. Al igual que con las expresiones de vinculación regulares, obtienes la seguridad nula y del subproceso de la vinculación de datos mientras se evalúan estas expresiones de los objetos de escucha.
En el ejemplo anterior, no se definió el parámetro view
que se pasa a onClick(View)
. Las vinculaciones de objetos de escucha proporcionan dos opciones para sus parámetros: puedes ignorar todos los parámetros del método o nombrarlos a todos. Si prefieres nombrar los parámetros, puedes usarlos en tu expresión. Por ejemplo, puedes escribir la expresión anterior de la siguiente manera:
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
Si quieres usar el parámetro en la expresión, puedes hacerlo de la siguiente manera:
Kotlin
class Presenter { fun onSaveClick(view: View, task: Task){} }
Java
public class Presenter { public void onSaveClick(View view, Task task){} }
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
Además, puedes usar una expresión lambda con más de un parámetro:
Kotlin
class Presenter { fun onCompletedChanged(task: Task, completed: Boolean){} }
Java
public class Presenter { public void onCompletedChanged(Task task, boolean completed){} }
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
Si el evento que estás escuchando muestra un valor cuyo tipo no es void
, tus expresiones también deben mostrar el mismo tipo de valor. Por ejemplo, si deseas escuchar el evento de mantener presionado (clic largo), tu expresión debe mostrar un valor booleano.
Kotlin
class Presenter { fun onLongClick(view: View, task: Task): Boolean { } }
Java
public class Presenter { public boolean onLongClick(View view, Task task) { } }
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
Si la expresión no se puede evaluar debido a objetos null
, la vinculación de datos muestra el valor predeterminado para ese tipo, como null
para los tipos de referencia, 0
para int
o false
para boolean
.
Si necesitas usar una expresión con un predicado, por ejemplo, un ternario, puedes usar void
como símbolo:
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Evita usar objetos de escucha complejos
Las expresiones de los objetos de escucha son potentes y pueden facilitar la lectura de tu código. Por otro lado, los objetos de escucha que contienen expresiones complejas dificultan la lectura y el mantenimiento de tus diseños más. Haz que tus expresiones sean tan simples como pasar los datos disponibles de tu IU a tu método de devolución de llamada. Implementa cualquier lógica empresarial dentro del método de devolución de llamada que invoques desde la expresión del objeto de escucha.
Importaciones, variables e inclusiones
La biblioteca de vinculación de datos proporciona funciones como importaciones, variables e inclusiones. Las importaciones hacen que las clases sean fáciles de consultar dentro de tus archivos de diseño. Las variables te permiten describir una propiedad que se puede usar en expresiones de vinculación. Las inclusiones permiten reutilizar diseños complejos en tu app.
Importaciones
Las importaciones te permiten hacer referencia a clases dentro de tu archivo de diseño, como en el código administrado.
Puedes usar cero o más elementos import
dentro del elemento data
. En el siguiente ejemplo de código, se importa la clase View
al archivo de diseño:
<data>
<import type="android.view.View"/>
</data>
Importar la clase View
te permite hacer referencia a ella desde tus expresiones de vinculación.
En el siguiente ejemplo, se muestra cómo hacer referencia a las constantes VISIBLE
y GONE
de la clase View
:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
Escribe alias
Cuando hay conflictos de nombre de clase, puedes cambiar el nombre de una de las clases por un alias. En el siguiente ejemplo, se cambia el nombre de la clase View
en el paquete com.example.real.estate
a Vista
:
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
Luego, puedes usar Vista
para hacer referencia a com.example.real.estate.View
y View
para hacer referencia a android.view.View
dentro del archivo de diseño.
Importa otras clases
Puedes usar tipos importados como referencias de tipo en variables y expresiones. En el siguiente ejemplo, se muestran los elementos User
y List
que se usan como el tipo de una variable:
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>
Puedes usar los tipos importados para transmitir parte de una expresión. En el siguiente ejemplo, se convierte la propiedad connection
a un tipo de User
:
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
También puedes usar tipos importados cuando haces referencia a campos y métodos estáticos en las expresiones. En el siguiente código, se importa la clase MyStringUtils
y se hace referencia a su método capitalize
:
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
</data>
…
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Al igual que en el código administrado, se importa automáticamente java.lang.*
.
Variables
Puedes usar varios elementos variable
dentro del elemento data
. Cada elemento variable
describe una propiedad que se puede configurar en el diseño para usar en expresiones de vinculación dentro del archivo de diseño. En el siguiente ejemplo, se declaran las variables user
, image
y note
:
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
Los tipos de variables se inspeccionan en el tiempo de compilación, por lo que, si una variable implementa Observable
o es una colección observable, esto debe reflejarse en el tipo. Si la variable es una interfaz o clase base que no implementa la interfaz Observable
, no se observan las variables.
Cuando hay diferentes archivos de diseño para varias configuraciones (por ejemplo, horizontal o vertical), las variables se combinan. No debe haber definiciones de variables conflictivas entre estos archivos de diseño.
La clase de vinculación generada tiene un método set y un método get para cada una de las variables descritas. Las variables toman los valores de código administrados predeterminados hasta que se llama al método set: null
para los tipos de referencia, 0
para int
, false
para boolean
, etcétera.
Se genera una variable especial llamada context
para usar en expresiones de vinculación según sea necesario. El valor de context
es el objeto Context
del método getContext()
de la vista raíz. La variable context
se anula con una declaración de variable explícita con ese nombre.
Inclusiones
Puedes pasar variables a la vinculación de un diseño incluido desde el diseño que las contiene mediante el espacio de nombres de la app y el nombre de la variable en un atributo. En el siguiente ejemplo, se muestran las variables user
incluidas de los archivos de diseño name.xml
y contact.xml
:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
La vinculación de datos no admite una inclusión como elemento secundario directo de un elemento de combinación. Por ejemplo, el siguiente diseño no es compatible:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<merge><!-- Doesn't work -->
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</merge>
</layout>
Recursos adicionales
Para obtener más información sobre la vinculación de datos, consulta los siguientes recursos adicionales.