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
- Amostras da Android Data Binding Library (link em inglês)
Codelabs
Postagens do blog
- Vinculação de dados: lições aprendidas (link em inglês)
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