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)
Nenhuma recomendação no momento.
Tente fazer login na sua Conta do Google.