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