Data Binding 2: Binding Adapters

ANUNCIO
Loading...

Resumen: en este tutorial, aprenderás a usar Binding Adapters para personalizar la lógica de asignación de un valor en un atributo de un view.

Seguido verás cómo usar Binding Methods para redireccionar los métodos set*() de atributos cuyo nombre no coincide con estos.

También aprenderás a convertir datos en las expresiones de vinculación con Binding Converters.

No olvides leer la parte 1 antes:

Binding Methods

Un binding method o método de vinculación soluciona el escenario en que la librería de Data Binding no encuentra el setter de un atributo.

¿Por qué sucede esto?

Porque la librería busca el método con la convención de nombrado setNombreAtributo() para ejecutarlo y en algunos casos esto no se cumple.

Ejemplo:

Para android:tint en un ImageView la librería espera encontrar setTint(), pero este no existe. El método es setImageTintList().

O con app:srcCompat espera setSrcCompat(), pero resulta que es setImageResource().

Por lo que si intentas usar el lenguaje de expresión en estos campos no habrá resultados.

Solución:

Crear una anotación @BindingMethods que contenga anotaciones @BindingMethod describiendo la redirección del método set en el atributo.

@androidx.databinding.BindingMethods({ @BindingMethod(type = ImageView.class, attribute = "android:tint", method = "setImageTintList"), @BindingMethod( type = ImageView.class, attribute = "app:srcCompat", method = "setImageResource") }) public class BindingMethods { }
Lenguaje del código: CSS (css)

Como ves, la clase BindingMethods tiene dos anotaciones para los casos mencionados anteriormente.

@BindingMethod necesita la clase donde está el atributo, el atributo y el método que será interpretado como setter (type, attribute y method)

Binding Adapters

Un Binding Adapter te permite personalizar la lógica con la que un método set se ejecuta para un atributo.

¿Cómo hacerlo?

Dentro de una clase añade un método estático público de retorno void y anótalo con @BindingAdapter.

public class BindinAdapters { @BindingAdapter("nombre_atributo") public static void setNombreAtributo(View v, int otroParametro){ // acción set*() } }
Lenguaje del código: PHP (php)

La anotación debe recibir un parámetro asociado al view por editar.

El método debe recibir como primer parámetro el tipo de view a modificar.

Y si lo requieres añade más parámetros de los cuales dependa la asignación.

Ejemplo:

Mostrar/ocultar un view que representa la ausencia de datos con el atributo android:visibility:

@BindingAdapter("android:visibility") public static void showEmptyState(View v, boolean show) { v.setVisibility(show ? View.VISIBLE : View.GONE); }
Lenguaje del código: JavaScript (javascript)

Luego pasaríamos el parámetro show en una expresión de binding, suponiendo que existe una variable items del tipo List, evaluando si no tiene items:

android:visibility="@{items.size()==0}"
Lenguaje del código: JavaScript (javascript)

Binding Adapter Con Múltiples Parámetros

Los adaptadores también definir parámetros adicionales para el view con el fin de personalizar aún más la lógica de asignación.

Ejemplo:

Setear en un TextView la información de un paciente a través de su nombre, fecha de nacimiento e historial clínico:

@BindingAdapter({"name", "birth_date", "medical_records"}) public static void setBio(TextView textView, String name, Date birthDate, List<MedicalRecord> records) { StringBuilder sb = new StringBuilder("Nombre: "); sb.append(name); sb.append("n"); sb.append("Edad: "); sb.append(String.valueOf(AgeCalculator.calculateAge(birthDate))); sb.append("n"); sb.append("Historial Médico: n"); for (MedicalRecord record : records) { sb.append("-"+record.getDescription()+"n"); } textView.setText(sb.toString()); }
Lenguaje del código: JavaScript (javascript)

Como ves, la anotación recibe tres parámetros sin namespace, los cuales serán usados en el text view.

Los parámetros de la anotación deben coincidir con la cantidad de los del método sin contar la instancia del view.

La idea del ejemplo es crear un String formateado con cada parámetro y asignarlo con setText() al final. La forma de pasarlo en XML sería:

<TextView app:name="@{patient.name}" app:birth_date="@{patient.birthDate}" app:medical_records="@{patient.medicalRecords}"/>
Lenguaje del código: HTML, XML (xml)

Usamos el app como namespace y escribimos el nombre del parámetro del adaptador y asignamos los valores de nuestras variables.

Binding Converters Personalizados

Permiten convertir de un tipo a otro en las expresiones de binding, dependiendo de la lógica que establezcamos.

La librería buscará automáticamente estos convertidores y los aplicará.

Para definirlos anota con @BindingConversion a un método publico estático en alguna clase. Especifica como parámetro el tipo entrante y el retorno como el tipo resultante.

public class BindingConverters { @BindingConversion public static Tipo1 tipo1ATipo2 (Tipo2 tipo2){ // lógica de conversión return tipo1; } }
Lenguaje del código: PHP (php)

Ejemplos De Binding Adapters, Methods Y Converters

Usaremos una app de ejemplo que representa la creación de una cuenta en un servicio hipotético.

Ejemplo de Binding Adapters

El proyecto consta de una sola actividad (MainActivity) y tres clases para los elementos de binding:

El layout de la actividad sin binding es el siguiente:

<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" tools:context=".ui.MainActivity"> <ImageView android:id="@+id/create_account_image" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" app:layout_constraintBottom_toBottomOf="@+id/welcome_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guideline" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/name_field" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:ems="10" android:hint="@string/name_field_text" android:inputType="textPersonName" app:layout_constraintBottom_toTopOf="@+id/email_field" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/welcome_text" /> <EditText android:id="@+id/email_field" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:ems="10" android:hint="@string/email_field_text" android:inputType="textEmailAddress" app:layout_constraintBottom_toTopOf="@+id/password_field" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/name_field" /> <EditText android:id="@+id/password_field" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:ems="10" android:hint="@string/password_field_hint" android:inputType="textPassword" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/email_field" /> <Button android:id="@+id/sign_up_button" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="@string/sign_up_button_text" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/industry_menu" /> <TextView android:id="@+id/have_account_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:text="@string/login_support_text" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintBottom_toTopOf="@+id/login_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/sign_up_button" app:layout_constraintVertical_bias="1.0" /> <Button android:id="@+id/login_button" style="@style/Widget.AppCompat.Button.Borderless" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/login_button_text" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" /> <Spinner android:id="@+id/industry_menu" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/industry_label" tools:entries="@tools:sample/us_zipcodes" /> <TextView android:id="@+id/create_account_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/create_account_text" android:textAppearance="@style/TextAppearance.AppCompat.Title" android:textColor="@android:color/black" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/welcome_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginEnd="8dp" android:layout_marginRight="8dp" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintEnd_toStartOf="@+id/guideline" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/create_account_title" tools:text="@string/day_message" /> <ImageView android:id="@+id/time_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginLeft="8dp" app:layout_constraintBottom_toBottomOf="@+id/create_account_title" app:layout_constraintEnd_toStartOf="@+id/guideline" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toEndOf="@+id/create_account_title" tools:srcCompat="@drawable/ic_day" /> <TextView android:id="@+id/industry_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="@string/industry_label" android:textAppearance="@style/TextAppearance.AppCompat.Body1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/password_field" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.6" /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:visibility="gone" /> <androidx.constraintlayout.widget.Group android:id="@+id/group" android:layout_width="wrap_content" android:layout_height="wrap_content" app:constraint_referenced_ids="have_account_label,password_field,create_account_image, @+id/name_field,time_icon,create_account_title,industry_label,welcome_text,sign_up_button, industry_menu,guideline,email_field,login_button,name_field" tools:layout_editor_absoluteX="16dp" tools:layout_editor_absoluteY="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
Lenguaje del código: HTML, XML (xml)

La previsualización mostraría lo siguiente:

Como en la parte 1, conviértelo en un layout para Data Binding y reemplaza el inflado de la actividad.

Usar Un Binding Method Para app:srcCompat

Si deseamos asignar dinámicamente el drawable para los vectores del tiempo, necesitamos redirigir al atributo app:srcCompat hacia el método setImageResource():

@androidx.databinding.BindingMethods({ @BindingMethod( type = ImageView.class, attribute = "srcCompat", method = "setImageResource") }) public class BindingMethods { }
Lenguaje del código: CSS (css)

Crear Binding Adapter Para Cargar Imagen Con Glide

Cargaremos la imagen del logo desde un drawable con la librería Glide. La idea es asignarle el resultado a create_account_image.

El binding adapter requiere el identificador entero del drawable, por lo que ese será el parámetro a usar:

@BindingAdapter("drawable") public static void loadLogo(ImageView imageView, Drawable drawable) { Glide.with(imageView).load(drawable).into(imageView); }
Lenguaje del código: CSS (css)

En seguida pasa la referencia del drawable que tenemos en el proyecto usando el operador @drawable.

<ImageView android:id="@+id/create_account_image" app:drawable="@{@drawable/create_account_image}" />
Lenguaje del código: HTML, XML (xml)

Crear Binding Adapter Para Elegir El Vector

Dependiendo de la variable booleana day, así mismo usaremos un icono de sol u otro de luna.

<variable name="day" type="Boolean" />
Lenguaje del código: HTML, XML (xml)

El adaptador se vería así:

@BindingAdapter("srcCompat") public static void setSrcCompat(ImageView imageView, boolean day) { imageView.setImageDrawable(day ? ContextCompat.getDrawable(imageView.getContext(), R.drawable.ic_day) : ContextCompat.getDrawable(imageView.getContext(), R.drawable.ic_night) ); }
Lenguaje del código: CSS (css)

Si el valor es verdadero, cargaremos el drawable ic_day, de lo contrario cargaremos ic_night; la asignación la realizamos con setImageDrawable().

La expresión de binding en el view sería:

<ImageView android:id="@+id/time_icon" app:srcCompat="@{day}" />
Lenguaje del código: HTML, XML (xml)

Crear Binding Adapter Para Color De Vector

Los vectores que representan la noche y el día tienen un background de color negro. Cambiaremos el tinte del vector a través del atributo android:tint dependiendo de si es día o noche.

@BindingAdapter("android:tint") public static void setTint(ImageView imageView, boolean day) { ImageViewCompat.setImageTintList( imageView, ColorStateList.valueOf( day ? ContextCompat.getColor(imageView.getContext(), R.color.day_color) : ContextCompat.getColor(imageView.getContext(), R.color.night_color) )); }
Lenguaje del código: JavaScript (javascript)

Recibimos el mismo parámetro booleano y referenciamos a android:tint. La idea es usar ImageViewCompat.setImageTintList() para asignar el ColorStateList que resulte de day_color o night_color.

Al pasar el valor tenemos:

<ImageView android:id="@+id/time_icon" android:tint="@{day}" />
Lenguaje del código: HTML, XML (xml)

Crear Binding Adapter Para Del Spinner

Asignar los items del Spinner se logra con android:entries y la variable industry_options.

<data> <import type="java.util.List" /> <variable name="industry_options" type="List<String>" /> </data>
Lenguaje del código: HTML, XML (xml)

Debido a que no existe un método set para este, crearemos un binding adapter que genere la asignación:

@BindingAdapter("android:entries") public static void setEntries(Spinner spinner, List<String> entries){ ArrayAdapter<String> adapter = new ArrayAdapter<>( spinner.getContext(), android.R.layout.simple_spinner_item, entries ); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); }
Lenguaje del código: JavaScript (javascript)

La siguiente es la declaración XML:

<Spinner android:id="@+id/industry_menu" android:entries="@{industry_options}" />
Lenguaje del código: HTML, XML (xml)

Crear Binding Converter De Booleanos A Enteros

La visibilidad requiere de banderas enteras que representan el estado del view. Si deseamos pasar un booleano en su lugar para el uso, añadimos un conversor que determine la correlación de valores:

public class BindingConverters { @BindingConversion public static int booleanToVisibility(boolean show) { return show ? View.VISIBLE : View.GONE; } }
Lenguaje del código: PHP (php)

Como ves, positivo es VISIBLE y negativo GONE.

Si te fijas, nuestro layout tiene un grupo que sostiene todos los views de la jerarquía para que compartan su visibilidad.

En el momento en que se de click en el botón de registro, se mostrará una progress bar y los demás views desaparecerán.

Para lograr esto, usamos la variable booleana show en android:visibility del grupo y la barra de progreso de la siguiente forma:

<ProgressBar android:id="@+id/progressBar" android:visibility="@{!show}" /> <androidx.constraintlayout.widget.Group android:id="@+id/group" android:visibility="@{show}" />
Lenguaje del código: HTML, XML (xml)

Al pasar el valor booleano, nuestro converter hará el trabajo de asignación.

Binding En La Actividad

Finalmente en la actividad ligaremos los parámetros:

public class MainActivity extends AppCompatActivity { private ActivityMainBinding mBinding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // vincular root del layout mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); // crear adaptador de spinner ArrayList<String> industryOptions = new ArrayList<>(); industryOptions.add("Economía"); industryOptions.add("Computación"); industryOptions.add("Bienes raíces"); industryOptions.add("Salud"); // ligar variables mBinding.setDay(getHourOfDay()); mBinding.setIndustryOptions(industryOptions); mBinding.setHandler(this); mBinding.setShow(true); } private boolean getHourOfDay() { // obtener hora del día Calendar c = Calendar.getInstance(); int hourOfDay = c.get(Calendar.HOUR_OF_DAY); return hourOfDay > 0 && hourOfDay < 18; } public void signUp(View button) { mBinding.setShow(false); new Handler().postDelayed(new Runnable() { @Override public void run() { mBinding.setShow(true); } }, 3000); } }
Lenguaje del código: JavaScript (javascript)

Se crea la lista del spinner con 4 valores de ejemplo, también usamos el método getHourOfDay() para conseguir la hora del día y verificar si es de día o de noche.

Y por último controlamos la visibilidad de la barra de progreso con signUp(), el cual está ligado al botón.

<Button android:id="@+id/sign_up_button" android:onClick="@{handler::signUp}"/>
Lenguaje del código: HTML, XML (xml)

Al presionar el botón se intercambian las visibilidades.

Ejemplo android:visibility con Binding Converter

Descargar Código

Suscríbete y obtén el código gratis.

[sociallocker id=»7121″]Descargar Gratis[/sociallocker]

¿Ha sido útil esta publicación?