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 é ligeiramente 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 mais recente, 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.