Os adaptadores de vinculação são responsáveis por fazer as chamadas de framework adequadas para
definir valores. Um exemplo é definir um valor de propriedade, como chamar o
método setText()
. Outro
exemplo é definir um listener de eventos, como chamar o
método
setOnClickListener()
.
A biblioteca Data Binding 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.
Definir 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 biblioteca Data Binding defina automaticamente o método ou declarar explicitamente o método 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 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 vai procurar um método setText()
que aceite um argumento String
. Se a
expressão retornar um int
, a biblioteca vai procurar um método setText()
que
aceite um argumento int
. A expressão precisa retornar o tipo correto. É possível
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. É possível
criar atributos para qualquer setter usando a vinculação de dados. Por exemplo, a classe
de suporte
DrawerLayout
não tem atributos, mas tem muitos setters. O layout abaixo
usa automaticamente os métodos
setScrimColor(int)
e
addDrawerListener(DrawerListener)
como setter dos atributos app:scrimColor
e 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, 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. Métodos de vinculação são anotações que
você pode adicionar a qualquer classe do seu app.
No exemplo a seguir, o atributo android:tint
está associado ao método
setImageTintList(ColorStateList)
,
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"), })
Normalmente, não é necessário renomear setters em classes de framework do Android. Os atributos já foram implementados usando a convenção de nomes para encontrar automaticamente os 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 associado
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
a anotação BindingAdapter
permite personalizar a forma como o setter de um atributo é chamado.
Os atributos das classes do framework do Android já têm anotações
BindingAdapter
. 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 o atributo determinado.
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 worker para carregar uma imagem.
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); }
Você pode usar o adaptador no layout, conforme mostrado no exemplo abaixo. Observe
que @drawable/venueError
se refere a um recurso no seu app. Ao envolvê-lo com @{}
, ele se 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 um objeto
ImageView
, se imageUrl
for uma
string e se error
for uma
Drawable
. Se você quiser
que o adaptador seja chamado quando qualquer um dos atributos estiver definido, defina a flag
requireAll
opcional 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); } }
Os métodos do adaptador de vinculação podem usar os valores antigos nos gerenciadores. Um método que aceita valores antigos e novos precisa declarar primeiro todos os valores antigos dos atributos, seguidos pelos novos valores, como mostrado no exemplo abaixo:
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 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 gerenciadores
deles:
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, você precisa de um adaptador que
funcione para um dos atributos ou para ambos. Você pode definir 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 abaixo.
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 os métodos
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 eles possam ser removidos no adaptador de vinculação.
Conversões de objeto
Conversão automática de objetos
Quando uma Object
é retornada de uma expressão
de vinculação, a biblioteca seleciona 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, conforme mostrado no exemplo abaixo.
<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 é transmitido automaticamente
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, 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. 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 abaixo mostra um
atributo que espera um Drawable
, mas 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
for esperado e um número inteiro for retornado, converta o int
em um ColorDrawable
.
Para realizar a conversão, use um método estático com uma anotação
BindingConversion
, desta 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, conforme mostrado no exemplo a seguir:
// 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 a vinculação de dados, consulte os recursos a seguir.
Exemplos
- Exemplos da biblioteca Android Data Binding (link em inglês)
Codelabs
Postagens do blog
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Biblioteca Data Binding
- Layouts e expressões de vinculação
- Vinculação de visualizações