O segundo Visualização do Desenvolvedor do Android 11 já está disponível, teste e compartilhe seu feedback.

Como vincular adaptadores

Os adaptadores de vinculação são responsáveis por realizar as chamadas de framework adequadas para definir valores. Um exemplo é a configuração de um valor de propriedade como a chamada para o método setText(). Outro exemplo é a definição de um listener de eventos como a chamada para o método setOnClickListener().

A Data Binding Library permite especificar o método chamado para definir um valor, fornecer sua própria lógica de vinculação e especificar o tipo de objeto retornado usando adaptadores.

Configurar valores de atributos

Sempre que um valor vinculado muda, a classe de vinculação gerada precisa chamar um método setter na visualização com a expressão de vinculação. Você pode permitir que a Data Binding Library determine automaticamente o método, declare-o explicitamente ou forneça uma lógica personalizada para selecionar um método.

Seleção automática de método

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

Por exemplo, considerando a expressão android:text="@{user.name}", a biblioteca procura um método setText(arg) que aceite o tipo retornado por user.getName(). Se o tipo de retorno de user.getName() for String, a biblioteca procurará um método setText() que aceite um argumento String. Se, em vez disso, a expressão retornar um int, a biblioteca procurará um método setText() que aceite um argumento int. A expressão precisa retornar o tipo correto. Você pode transmitir 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, a classe de compatibilidade DrawerLayout não tem atributos, mas tem muitos setters. O layout a seguir usa automaticamente os métodos setScrimColor(int) e setDrawerListener(DrawerListener) como o setter para os atributos app:scrimColor e 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}">
    

Especificar um nome de método personalizado

Alguns atributos têm setters que não correspondem ao nome. Nessas situações, um atributo pode ser associado ao setter usando a anotação BindingMethods. A anotação é usada com uma classe e pode conter várias anotações BindingMethod, uma para cada método renomeado. Os métodos de vinculação são anotações que podem ser adicionadas a qualquer classe do seu app. No exemplo a seguir, o atributo android:tint está associado ao método setImageTintList(ColorStateList), e não ao 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"),
    })

    

Na maior parte do tempo, não é necessário renomear os setters nas classes do framework do Android. Os atributos já foram implementados usando a convenção de nomes para encontrar métodos correspondentes automaticamente.

Fornecer uma lógica personalizada

Alguns atributos precisam de uma lógica de vinculação personalizada. Por exemplo, não há nenhum setter associado ao 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 a anotação BindingAdapter permite personalizar a forma como o setter de um atributo será chamado.

Os atributos das classes do framework do Android já têm anotações BindingAdapter criadas. O exemplo a seguir mostra o adaptador de vinculação para o 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 de visualização associada ao atributo. O segundo parâmetro determina o tipo aceito na expressão de vinculação para esse atributo específico.

Os adaptadores de vinculação são úteis para outros tipos de personalização. Por exemplo, um loader personalizado pode ser chamado a partir de uma linha de execução de um worker para carregar uma imagem.

Os adaptadores de vinculação definidos substituem os adaptadores padrão fornecidos pelo framework do Android quando há conflito.

Você também pode ter adaptadores que recebem vários atributos, conforme mostrado no 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);
    }

    

É possível usar o adaptador no seu layout, conforme mostrado no exemplo a seguir. Observe que @drawable/venueError se refere a um recurso no seu app. Para que o recurso se torne uma expressão de vinculação válida, é necessário envolvê-lo com @{}.

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

O adaptador será chamado se imageUrl e error forem usados para um objeto ImageView e se imageUrl for uma string e o error for um Drawable. Caso você queira que o adaptador seja chamado quando qualquer um dos atributos for definido, é possível definir a sinalização opcional requireAll do adaptador como 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);
      }
    }

    

Opcionalmente, os métodos do adaptador de vinculação podem aceitar os valores antigos nos manipuladores deles. Um método que aceita valores antigos e novos precisa declarar primeiro todos os valores antigos dos atributos, seguidos pelos novos valores, conforme mostrado no exemplo abaixo:

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

    

Os manipuladores de eventos só podem ser usados com interfaces ou classes abstratas com um método abstrato, conforme 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 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 em um listener também pode afetar o outro, você precisa de um adaptador que funcione para um dos atributos ou para ambos. Você pode configurar requireAll como false na anotação para especificar que nem todos os atributos precisam receber uma expressão de 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 mais complicado que o normal porque a classe View usa os métodos addOnAttachStateChangeListener() e removeOnAttachStateChangeListener(), em vez de um método setter para OnAttachStateChangeListener. A classe android.databinding.adapters.ListenerUtil ajuda a rastrear os listeners anteriores para que eles possam ser removidos no adaptador de vinculação.

Ao fazer anotações nas interfaces OnViewDetachedFromWindow e OnViewAttachedToWindow com @TargetApi(VERSION_CODES.HONEYCOMB_MR1), o gerador de código de vinculação de dados sabe que o listener só pode ser gerado ao ser executado no Android 3.1 (API de nível 12) ou posteriores, que são as mesmas versões compatíveis com o método addOnAttachStateChangeListener().

Conversões de objeto

Conversão automática de objetos

Quando um Object é retornado de uma expressão de vinculação, a biblioteca escolhe o método usado para definir o valor da propriedade. O Object é transmitido para um tipo de parâmetro do método escolhido. Esse comportamento é conveniente em apps que usam a classe ObservableMap para armazenar dados, como mostrado no exemplo a seguir:

<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 transmitido para o tipo de parâmetro encontrado no método setText(CharSequence), usado para definir o valor do atributo android:text. Se o tipo de parâmetro for ambíguo, será necessário transmitir o tipo de retorno na expressão.

Conversões personalizadas

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

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

Sempre que um Drawable é esperado e um número inteiro é retornado, o int precisa ser convertido em um ColorDrawable. A conversão pode ser feita usando um método estático com uma anotação BindingConversion, da seguinte maneira:

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, conforme mostrado no exemplo a seguir:

<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 seguintes recursos adicionais.

Amostras

Codelabs

Publicações do blog