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
- 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ê
Data Binding Library
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.
Layouts e expressões de vinculação
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.
Vinculação de visualizações
Discover the latest app development tools, platform updates, training, and documentation for developers across every Android device.