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():

@BindingMethods(value = [
   
BindingMethod(
        type
= android.widget.ImageView::class,
        attribute
= "android:tint",
        method
= "setImageTintList")])

@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:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view
.setPadding(padding,
                view
.getPaddingTop(),
                view
.getPaddingRight(),
                view
.getPaddingBottom())
}

@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:

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
   
Picasso.get().load(url).error(error).into(view)
}

@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:

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

@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:

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
   
if (oldPadding != newPadding) {
        view
.setPadding(newPadding,
                    view
.getPaddingTop(),
                    view
.getPaddingRight(),
                    view
.getPaddingBottom())
   
}
}

@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:

@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)
       
}
   
}
}
@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:

// 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)
}
@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:

@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)
       
}
   
}
}
@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:

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
@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

Nenhuma recomendação no momento.

Tente na sua Conta do Google.