Los adaptadores de vinculación se encargan de realizar las llamadas de framework apropiadas para establecer valores. Un ejemplo es establecer un valor de propiedad, como llamar al método setText()
. Otro ejemplo es configurar un objeto de escucha de eventos, como llamar al método setOnClickListener()
.
La biblioteca de vinculación de datos te permite especificar el método al que se llama para establecer un valor, proporcionar tu propia lógica de vinculación y especificar el tipo de objeto mostrado mediante adaptadores.
Cómo establecer valores de atributo
Cada vez que un valor vinculado cambia, la clase de vinculación generada debe invocar un método set en la vista con la expresión de vinculación. Puedes permitir que la biblioteca de vinculación de datos determine automáticamente el método, declare de manera explícita el método o proporcione una lógica personalizada para seleccionar un método.
Selección automática de métodos
Para un atributo llamado example
, la biblioteca intenta encontrar automáticamente setExample(arg)
que acepta tipos compatibles como el argumento. No se considera el espacio de nombres del atributo, sino que solo se utilizan el nombre y el tipo de atributo cuando se busca un método.
Por ejemplo, con la expresión android:text="@{user.name}"
, la biblioteca busca un método setText(arg)
que acepte el tipo que muestra user.getName()
. Si el tipo de datos que se muestra de user.getName()
es String
, la biblioteca busca un método setText()
que acepte un argumento String
. Si la expresión muestra un int
en su lugar, la biblioteca busca un método setText()
que acepte un argumento int
. La expresión debe mostrar el tipo correcto; puedes transmitir el valor de resultado si es necesario.
La vinculación de datos funciona incluso si no existe un atributo con el nombre dado. Luego, puedes crear atributos para cualquier método set mediante vinculaciones de datos. Por ejemplo, la clase de compatibilidad DrawerLayout
no tiene ningún atributo, pero sí muchos métodos set. En el siguiente diseño, se utilizan de forma automática los métodos setScrimColor(int)
y setDrawerListener(DrawerListener)
como métodos set para los atributos app:scrimColor
y app:drawerListener
, respectivamente:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
Especifica un nombre de método personalizado
Algunos atributos tienen métodos set que no coinciden por nombre. En esos casos, se puede asociar un atributo con el método set utilizando la anotación BindingMethods
. La anotación se usa con una clase y puede contener múltiples anotaciones de BindingMethod
, una para cada método al que se le cambió el nombre. Los métodos de vinculación son anotaciones que se pueden agregar a cualquier clase en tu app. En el siguiente ejemplo, el atributo android:tint
está asociado con el método setImageTintList(ColorStateList)
, no con setTint()
:
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
La mayoría de las veces no es necesario cambiar el nombre de los métodos set en las clases de marco de trabajo de Android. Los atributos ya se implementaron utilizando la convención de nombres para buscar automáticamente métodos coincidentes.
Proporciona lógica personalizada
Algunos atributos requieren una lógica de vinculación personalizada. Por ejemplo, no hay métodos set asociados para el atributo android:paddingLeft
. En su lugar, se proporciona el método setPadding(left,
top, right, bottom)
. Un método de adaptador de vinculación estático con la anotación BindingAdapter
permite personalizar el nombre de un método set para un atributo.
Los atributos de las clases de framework de Android ya tienen anotaciones BindingAdapter
. Por ejemplo, a continuación se muestra el adaptador de vinculación correspondiente al atributo paddingLeft
:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
Los tipos de parámetros son importantes. El primer parámetro determina el tipo de vista asociada con el atributo. El segundo parámetro determina el tipo aceptado en la expresión de vinculación para el atributo dado.
Los adaptadores de vinculación son útiles para otros tipos de personalización. Por ejemplo, se puede llamar a un cargador personalizado desde un subproceso de trabajo para cargar una imagen.
Los adaptadores de vinculación que defines anulan los adaptadores predeterminados proporcionados por el marco de trabajo de Android cuando hay un conflicto.
También puede haber adaptadores que reciban varios atributos, como se muestra en el siguiente ejemplo:
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
Puedes usar el adaptador en tu diseño como se muestra en el siguiente ejemplo. Ten en cuenta que @drawable/venueError
hace referencia a un recurso de tu app. Delimitar el recurso con @{}
hace que sea una expresión de vinculación válida.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
Se llama al adaptador si se usan tanto imageUrl
como error
para un objeto ImageView
, y imageUrl
es una string y error
es un Drawable
. Si quieres que se llame al adaptador cuando se establece cualquiera de los atributos, puedes configurar la marca opcional requireAll
del adaptador en false
, como se muestra en el siguiente ejemplo:
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Los métodos de adaptador de vinculación pueden tomar opcionalmente valores anteriores en sus controladores. Un método que toma valores antiguos y nuevos debe declarar todos los valores anteriores para los atributos primero, seguidos de los valores nuevos, como se muestra en el siguiente ejemplo:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@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 se pueden usar con interfaces o clases abstractas con un método abstracto, como se muestra en el siguiente ejemplo:
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@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); } } }
Usa este controlador de eventos en el diseño de la siguiente manera:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
Cuando un objeto de escucha tiene varios métodos, debe dividirse en múltiples objetos de escucha.
Por ejemplo, View.OnAttachStateChangeListener
tiene dos métodos: onViewAttachedToWindow(View)
y onViewDetachedFromWindow(View)
. La biblioteca proporciona dos interfaces para diferenciar los atributos y controladores que tienen:
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@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 cambiar un objeto de escucha también puede afectar al otro, necesitas un adaptador que funcione para cada atributo o para ambos. Puedes configurar requireAll
como false
en la anotación para especificar que no se debe asignar a todos los atributos una expresión de vinculación, como se muestra en el siguiente ejemplo:
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { 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); } } }; } 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 la clase View
usa los métodos addOnAttachStateChangeListener()
y removeOnAttachStateChangeListener()
en lugar de un método set para OnAttachStateChangeListener
. La clase android.databinding.adapters.ListenerUtil
ayuda a realizar un seguimiento de los objetos de escucha anteriores para que puedan quitarse en el adaptador de vinculación.
Cuando se anotan las interfaces OnViewDetachedFromWindow
y OnViewAttachedToWindow
con @TargetApi(VERSION_CODES.HONEYCOMB_MR1)
, el generador de código de vinculación de datos sabe que solo se debe generar el objeto de escucha cuando se ejecuta en Android 3.1 (API nivel 12) y versiones posteriores, la misma versión compatible con el método addOnAttachStateChangeListener()
.
Conversiones de objetos
Conversión automática de objetos
Cuando se muestra un Object
desde una expresión de vinculación, la biblioteca elige el método utilizado para establecer el valor de la propiedad. El Object
se transmite a un tipo de parámetro del método elegido. Este comportamiento es conveniente en apps que usan la clase ObservableMap
para almacenar datos, como se muestra en el siguiente ejemplo:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
El objeto userMap
de la expresión muestra un valor, que se convierte automáticamente al tipo de parámetro encontrado en el método setText(CharSequence)
, que se usa para establecer el valor del atributo android:text
. Si el tipo de parámetro es ambiguo, debes transmitir el tipo de datos que se muestra en la expresión.
Conversiones personalizadas
En algunos casos, se requiere una conversión personalizada entre tipos específicos. Por ejemplo, el atributo android:background
de una vista espera un Drawable
, pero el valor color
especificado es un número entero. El siguiente ejemplo muestra un atributo que espera un Drawable
, pero se proporciona un número entero:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Cada vez que se espera un Drawable
y se muestra un número entero, se debe convertir int
en ColorDrawable
. La conversión se puede realizar usando un método estático con una anotación BindingConversion
, de la siguiente manera:
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
Sin embargo, los tipos de valor proporcionados en la expresión de vinculación deben ser coherentes. No puedes usar tipos diferentes en la misma expresión, como se muestra en el siguiente ejemplo:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Recursos adicionales
Para obtener más información sobre la vinculación de datos, consulta los siguientes recursos adicionales.