Adaptadores de vinculação

Os adaptadores de vinculação são responsáveis por fazer as chamadas de framework adequadas para valores definidos. Um exemplo é definir um valor de propriedade, como chamar o método setText(). Outra exemplo é definir um listener de eventos, como chamar o setOnClickListener() .

A Data Binding Library permite que você especifique o método chamado para definir um valor, fornece sua própria lógica de vinculação e especifica o tipo do objeto retornado usando adaptadores.

Definir valores de atributos

Sempre que um valor vinculado mudar, a classe de vinculação gerada precisará chamar um setter. na visualização com a expressão de vinculação. Você pode deixar que a API Data Binding A biblioteca determina automaticamente o método, ou é possível declarar explicitamente o ou fornecer uma lógica personalizada para selecionar um método.

Seleção automática de método

Para um atributo chamado example, a biblioteca encontra automaticamente o método setExample(arg) que aceita tipos compatíveis como argumento. O namespace do atributo não é considerado. Apenas o nome e o tipo do atributo são usados ao pesquisar um método.

Por exemplo, considerando a expressão android:text="@{user.name}", a biblioteca procura por um método setText(arg) que aceite o tipo retornado por user.getName() Se o tipo de retorno de user.getName() for String, o procura por um método setText() que aceite um argumento String. Se o expressão retornar um int, a biblioteca pesquisa um método setText() que aceita um argumento int. A expressão precisa retornar o tipo correto. Você pode transmita o valor de retorno, se necessário.

A vinculação de dados funciona mesmo que não exista atributo com o nome específico. Você pode criar atributos para qualquer setter usando a vinculação de dados. Por exemplo, o suporte aula DrawerLayout não tem atributos, mas tem muitos setters. O layout a seguir usa automaticamente o setScrimColor(int) e addDrawerListener(DrawerListener) como o setter para o app:scrimColor e o app:drawerListener , respectivamente:

<androidx.drawerlayout.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

Especificar um nome de método personalizado

Alguns atributos têm setters que não correspondem ao nome. Nessas situações, uma atributo pode ser associado ao setter usando o BindingMethods uma anotação. A anotação é usada com uma classe e pode conter várias BindingMethod anotações, uma para cada método renomeado. Os métodos de vinculação são anotações que que você pode adicionar a qualquer classe em seu aplicativo.

No exemplo a seguir, o atributo android:tint está associado ao setImageTintList(ColorStateList) método, não com o 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"),
})

Normalmente, não é necessário renomear setters em classes de framework do Android. A atributos já foram implementados usando a convenção de nomes para automaticamente encontrar métodos correspondentes.

Fornecer uma lógica personalizada

Alguns atributos precisam de uma lógica de vinculação personalizada. Por exemplo, não há setter para o atributo android:paddingLeft. Em vez disso, o método setPadding(left, top, right, bottom) é fornecido. Um método de adaptador de vinculação estático com BindingAdapter permite que você personalize como um setter para um atributo é chamado.

Os atributos das classes do framework do Android já têm BindingAdapter. anotações. O exemplo a seguir mostra o adaptador de vinculação para a 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());
}

Os tipos de parâmetro são importantes. O primeiro parâmetro determina o tipo a visualização associada ao atributo. O segundo parâmetro determina o tipo aceito na expressão de vinculação para o atributo em questão.

Os adaptadores de vinculação também são úteis para outros tipos de personalização. Por exemplo: um carregador personalizado pode ser chamado a partir de uma linha de execução de trabalho para carregar uma imagem.

Você também pode ter adaptadores que recebem diversos atributos, conforme mostrado nas exemplo a seguir:

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

Você pode usar o adaptador no seu layout, conforme mostrado no exemplo a seguir. Observação que @drawable/venueError se refira a um recurso no app. Em torno do com @{} o torna uma expressão de vinculação válida.

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

O adaptador será chamado se imageUrl e error forem usados para uma objeto ImageView, imageUrl é um string, e error é um Drawable. Se você quiser adaptador que será chamado quando qualquer um dos atributos for definido, configure a requireAll do adaptador para false, conforme mostrado no exemplo a seguir:

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

Os métodos do adaptador de vinculação podem usar os valores antigos nos gerenciadores deles. Um método tomar valores antigos e novos deve declarar todos os valores antigos para os atributos primeiro, seguido pelos novos valores, conforme mostrado no exemplo a seguir:

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

Os manipuladores de eventos só podem ser usados com interfaces ou classes abstratas com um abstrato, como mostrado no exemplo a seguir:

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

Use esse manipulador de eventos no seu layout da seguinte forma:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

Quando um listener tem vários métodos, ele precisa ser dividido em vários listeners. Por exemplo: View.OnAttachStateChangeListener tem dois métodos: onViewAttachedToWindow(View) e onViewDetachedFromWindow(View). A biblioteca fornece duas interfaces para diferenciar os atributos e os manipuladores para eles:

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 a mudança de um listener pode afetar o outro, é necessário um adaptador que funciona para um dos atributos ou para os dois. Você pode definir requireAll como false em a anotação para especificar que nem todos os atributos precisam receber uma vinculação , conforme mostrado no exemplo a seguir:

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

O exemplo acima é um pouco complicado porque A classe View usa a addOnAttachStateChangeListener() e removeOnAttachStateChangeListener() em vez de um método setter para OnAttachStateChangeListener A classe android.databinding.adapters.ListenerUtil ajuda a monitorar esses listeners para que possam ser removidos no adaptador de vinculação.

Conversões de objeto

Conversão automática de objetos

Quando um Object é retornado de uma vinculação expressão, a biblioteca seleciona o método usado para definir o valor da . O Object é transmitido para um tipo de parâmetro do método escolhido. Isso comportamento em aplicativos que usam classe ObservableMap para armazenar dados, conforme mostrado neste exemplo:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

O objeto userMap na expressão retorna um valor, que é automaticamente transmitir para o tipo de parâmetro encontrado no método setText(CharSequence) usado para defina o valor do atributo android:text. Se o tipo de parâmetro for ambíguo, transmita o tipo de retorno na expressão.

Conversões personalizadas

Em algumas situações, uma conversão personalizada é necessária entre tipos específicos. Para exemplo, o atributo android:background de uma visualização espera um Drawable, mas o valor color especificado é um número inteiro. O exemplo a seguir mostra que espera um Drawable, mas, em vez disso, é fornecido um número inteiro:

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

Sempre que um Drawable for esperado e um número inteiro for retornado, converta a int. para um ColorDrawable. Para realizar a conversão, use um método estático com uma BindingConversion da seguinte forma:

Kotlin

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

Java

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

No entanto, os tipos de valores fornecidos na expressão de vinculação precisam ser consistentes. Não é possível usar tipos diferentes na mesma expressão, como mostrado exemplo:

// 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"/>

Outros recursos

Para saber mais sobre vinculação de dados, consulte os recursos a seguir.

Amostras

Codelabs

Postagens do blog