Adaptadores de vinculación

Los adaptadores de vinculación se encargan de realizar las llamadas de framework apropiadas a establecer valores. Un ejemplo es establecer un valor de propiedad, como llamar a setText(). Otro ejemplo es configurar un objeto de escucha de eventos, como llamar al setOnClickListener() .

La biblioteca de vinculación de datos te permite especificar el método llamado para establecer un valor, proporcionar tu propia lógica de vinculación y especificar el tipo de objeto que se devuelve con adaptadores.

Configura los valores de atributos

Cada vez que cambia un valor vinculado, la clase de vinculación generada debe llamar a un método set en la vista con la expresión de vinculación. Puedes permitir que la vinculación de datos La biblioteca determina automáticamente el método, o puedes declarar explícitamente o proporcionar una lógica personalizada para seleccionar un método.

Selección automática de métodos

Para un atributo llamado example, la biblioteca encuentra automáticamente el método setExample(arg), que acepta tipos compatibles como argumento. El espacio de nombres del atributo. Solo se usan 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 La biblioteca busca un método setText() que acepte un argumento String. Si el botón expresión muestra un int, la biblioteca busca un método setText() que acepta un argumento int. La expresión debe mostrar el tipo correcto. Puedes y convertir el valor de muestra si es necesario.

La vinculación de datos funciona incluso si no existe un atributo con el nombre dado. Puedes crear atributos para cualquier método set con vinculación de datos Por ejemplo, la asistencia clase DrawerLayout no tiene atributos, pero tiene muchos métodos set. El siguiente diseño utiliza automáticamente el setScrimColor(int) y addDrawerListener(DrawerListener) métodos como el método set para app:scrimColor y app:drawerListener. de atributos, respectivamente:

<androidx.drawerlayout.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 estas situaciones, un pueden asociarse al método set a través del BindingMethods . La anotación se usa con una clase y puede contener múltiples BindingMethod anotaciones, una para cada método renombrado. Los métodos de vinculación son anotaciones que que puedes agregar a cualquier clase en tu app.

En el siguiente ejemplo, el atributo android:tint está asociado con el setImageTintList(ColorStateList) no con el método 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"),
})

Por lo general, no es necesario cambiar el nombre de los métodos set en las clases del framework de Android. El ya se implementaron usando la convención de nombres para buscar métodos coincidentes.

Proporciona lógica personalizada

Algunos atributos requieren una lógica de vinculación personalizada. Por ejemplo, no hay datos asociados método set 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 el BindingAdapter te permite personalizar el nombre de un método set para un atributo.

Los atributos de las clases del framework de Android ya tienen BindingAdapter anotaciones. En el siguiente ejemplo, se muestra el adaptador de vinculación para la 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 la vista asociada con el atributo. El segundo parámetro determina el tipo aceptado en la expresión de vinculación para el atributo especificado.

Los adaptadores de vinculación tambié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.

También puedes tener adaptadores que reciban múltiples 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. Nota que @drawable/venueError hace referencia a un recurso de tu app. Alrededor de con @{} la convierte en una expresión de vinculación válida.

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

Se llama al adaptador si se usan imageUrl y error para un ImageView, imageUrl es un string y error es una Drawable. Si quieres el adaptador al que se llamará cuando se establezca cualquiera de los atributos, establece el estado requireAll marca del adaptador a 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 los valores anteriores en sus controladores. Un método Para tomar los valores antiguos y nuevos, primero se deben declarar todos los valores anteriores para los atributos. seguido 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(newPadding,
                    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 una 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 los controladores para ellos:

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);
}

Como cambiar un objeto de escucha puede afectar al otro, necesitas un adaptador funciona con un atributo o con ambos. Puedes configurar requireAll como false en la anotación para especificar que no a todos los atributos se les debe asignar una vinculación expresió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 complicado porque View usa el elemento addOnAttachStateChangeListener() y removeOnAttachStateChangeListener() en lugar de un método set para OnAttachStateChangeListener. La clase android.databinding.adapters.ListenerUtil ayuda a hacer un seguimiento de estos objetos de escucha para que puedan quitarse en el adaptador de vinculación.

Conversiones de objetos

Conversión automática de objetos

Cuando se muestra un Object desde una vinculación expresión, la biblioteca selecciona el método utilizado para establecer el valor de la propiedad. Object se transmite a un tipo de parámetro del método elegido. Esta el comportamiento de los usuarios es conveniente en las apps que usan 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 convertir al tipo de parámetro encontrado en el método setText(CharSequence) que se usa para configura el valor del atributo android:text. Si el tipo de parámetro es ambiguo, transmite 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. Para Por ejemplo, el atributo android:background de una vista espera un Drawable, pero El valor color especificado es un número entero. En el siguiente ejemplo, se muestra un que espera un Drawable, pero se proporciona un número entero en su lugar:

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

Cada vez que se espere un Drawable y se muestre un número entero, convierte int. a un elemento ColorDrawable. Para realizar la conversión, usa un método estático con una BindingConversion anotación, como se muestra a continuación:

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 a continuación ejemplo:

// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<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.

Ejemplos

Codelabs

Entradas de blog