Data Binding Library

Este documento explica como usar a Data Binding Library para escrever layouts declarativos e minimizar o código agrupador necessário para vincular a lógica e os layouts do aplicativo.

A Data Binding Library oferece flexibilidade e ampla compatibilidade — é uma biblioteca de apoio, útil com todas as versões da plataforma Android até o Android 2.1 (API de nível 7+).

Para usar vinculação de dados, é necessário o Android Plugin for Gradle 1.5.0-alpha1 ou posterior.

Ambiente de compilação

Para começar a usar o Data Binding, baixe a biblioteca do Support Repository no Android SDK Manager.

Para configurar o aplicativo para usar vinculação de dados, adicione o elemento dataBinding ao arquivo build.gradle no módulo do aplicativo.

Use o seguinte fragmento de código para configurar a vinculação de dados:

android {
    ....
    dataBinding {
        enabled = true
    }
}

Se você tem um módulo de aplicativo que depende de uma biblioteca que usa vinculação de dados, o módulo deve também configurar a vinculação de dados no arquivo build.gradle.

Além disso, certifique-se de usar uma versão compatível do Android Studio. O Android Studio 1.3 e versões posteriores são compatíveis com o Data Binding como descrito em Suporte do Android Studio ao Data Binding.

Arquivos de layout do Data Binding

Criação do primeiro conjunto de expressões de vinculação de dados

Os arquivos de layout de vinculação de dados são ligeiramente diferentes e começam com uma tag raiz layout, seguida de um elemento data e um elemento-raiz view. Esse elemento de visualização é o que a raiz deveria ser em um arquivo de layout sem vinculação. Eis um exemplo de arquivo:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

A variable de usuário dentro de data descreve uma propriedade que pode ser usada dentro desse layout.

<variable name="user" type="com.example.User"/>

Expressões dentro do layout são escritas nas propriedades dos atributos usando a sintaxe "@{}". Aqui, o texto de TextView é definido como a propriedade firstName do usuário:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>

Objeto de dados

Suponhamos que, por enquanto, você tem um objeto simples antigo Java (POJO) para o usuário:

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

Esse tipo de objeto tem dados que nunca mudam. É comum em aplicativos ter dados que são lidos uma vez e nunca mais mudam. Também é possível usar objetos JavaBeans:

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

Da perspectiva da vinculação de dados, essas duas classes são equivalentes. A expressão @{user.firstName} usada para o atributo android:text da TextView acessará o campo firstName na classe anterior e o método getFirstName() na classe posterior. Alternativamente, ele também será resolvido como firstName() se aquele método existir.

Vinculação de dados

Por padrão, as classe Binding são geradas com base no nome do arquivo de layout, convertendo-o para letras maiúsculas e minúsculas do Pascal e adicionando o sufixo "Binding". O arquivo de layout acima era main_activity.xml, portanto, a classe gerada foi MainActivityBinding. Essa classe contém todas as vinculações das propriedades de layout (por exemplo, a variável user) das visualizações do layout e sabe como atribuir valores para as expressões de vinculação. O meio mais fácil de criar as vinculações é ao inflar:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

Pronto! Execute o aplicativo e você verá o Usuário de Teste na IU. Outra alternativa para se obter a visualização é:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

Se estiver usando itens de vinculação de dados dentro de um adaptador ListView ou RecyclerView, pode ser preferível usar:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

Tratamento de eventos

O Data Binding permite compor expressões que tratam eventos despachados das visualizações (por exemplo, onClick). Os nomes de atributo dos eventos são governados pelo nome do método ouvinte, com algumas exceções. Por exemplo: View.OnLongClickListener tem um método onLongClick(), portanto o atributo para esse evento é android:onLongClick. Há duas formas de tratar um evento.

  • Referências do método: Em suas expressões, você pode referenciar métodos que estejam em conformidade com a assinatura do método ouvinte. Quando uma expressão é avaliada para uma referência de método, o Data Binding agrupa a referência do método e o objeto do proprietário em um ouvinte e define esse ouvinte na visualização-alvo. Se a expressão for avaliada como nulo, o Data Binding não criará um ouvinte e, em vez disso, definirá um ouvinte nulo.
  • Vinculações de ouvintes: São expressões lambda, avaliadas quando o evento acontece. O Data Binding sempre cria um ouvinte, que define na visualização. Quando o evento é despachado, o ouvinte avalia a expressão lambda.

Referências do método

É possível vincular diretamente os eventos a métodos do gerenciador, de forma semelhante a como se pode atribuir android:onClick a um método em uma atividade. Uma grande vantagem em comparação ao atributo View#onClick é que a expressão é processada em tempo de compilação, portanto, se o método não existe ou a assinatura não está correta, você recebe um erro em tempo de compilação.

A principal diferença entre Referências do método e Vinculações de ouvintes é que a implementação do ouvinte real é criada ao vincular dados, e não no acionamento do evento. Se você prefere avaliar a expressão quando o evento acontece, use vinculação de ouvintes.

Para atribuir um evento a seu gerenciador, use uma expressão de vinculação normal, com o valor sendo o nome do método a chamar. Por exemplo, se seu objeto de dados tem dois métodos:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

A expressão de vinculação pode atribuir um ouvinte de clique para uma visualização:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

Observe que a assinatura do método na expressão deve corresponder exatamente à assinatura do método no objeto ouvinte.

Vinculações de ouvintes

Vinculações de ouvintes são expressões de vinculação executadas quando um evento acontece. São semelhantes a referências do método, mas permitem executar expressões arbitrárias de vinculação de dados. Esse recurso está disponível com o Android Gradle Plugin para Gradle versões 2.0 e posteriores.

Em referências do método, os parâmetros do método devem corresponder aos parâmetros do ouvinte do evento. Em Vinculações de ouvintes, somente o valor de retorno deve corresponder ao valor de retorno esperado do ouvinte (a não ser que espere nulo). Por exemplo, pode-se ter uma classe apresentadora que tenha o seguinte método:

public class Presenter {
    public void onSaveClick(Task task){}
}
Em seguida, você pode vincular o evento de clique à classe da seguinte forma:
  <?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

Os ouvintes são representados por expressões lambda que têm permissão somente como elementos raiz de suas expressões. Quando um retorno de chamada é usado em uma expressão, o Data Binding automaticamente cria o ouvinte e os registradores necessários para o evento. Quando a visualização dispara o evento, o Data Binding avalia a expressão dada. Como em expressões de vinculação regulares, você ainda recebe o nulo e a segurança de encadeamento do Data Binding enquanto as expressões do ouvinte estão sendo avaliadas.

Observe que, no exemplo acima, não definimos o parâmetro view que é passado para onClick(android.view.View). Vinculações de ouvintes fornecem duas opções para parâmetros de ouvinte: você pode ignorar todos os parâmetro do método ou nomear todos eles. Se você preferir nomear os parâmetros, poderá usá-los em sua expressão. Por exemplo, a expressão acima poderia ser escrita como:

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"
Ou, se você quiser usar o parâmetro na expressão, poderia funcionar assim:
public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
É possível uma expressão lambda com mais de um parâmetro:
public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

Se o evento que você estiver ouvindo retornar um valor cujo tipo não é void, as expressões deverão também retornar o mesmo tipo. Por exemplo: se você quiser escutar o evento de clique longo, a expressão deverá retornar boolean.

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

Se a expressão não pode ser avaliada devido a objetos null, o Data Binding retorna o valor Java padrão para aquele tipo. Por exemplo: null para tipos de referência, 0 para int, false para boolean etc.

Se for necessário usar uma expressão com um predicado (por exemplo, ternário), você poderá usar void como um símbolo.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Evite ouvintes complexos
Expressões de ouvintes são muito poderosas e podem tornar o código muito fácil de ler. Por outro lado, ouvintes contendo expressões complexas tornam os layouts difíceis de ler e impossíveis de manter. Essas expressões devem ser tão simples quanto passar dados disponíveis da IU para o método de retorno de chamada. Você deve implementar a lógica de negócio dentro do método de retorno de chamada invocado na expressão do ouvinte.

Existem alguns gerenciadores especializados de eventos de clique e eles precisam de um atributo diferente de android:onClick para evitar conflito. Os seguintes atributos foram criados para evitar tais conflitos:

Classe Configurador do ouvinte Atributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Detalhes de layout

Importações

É possível usar zero ou mais elementos import dentro do elemento data. Eles permitem referências fáceis a classes dentro do arquivo de layout, como no Java.

<data>
    <import type="android.view.View"/>
</data>

Agora a visualização pode ser usada dentro da expressão de vinculação:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

Quando há conflitos de nomes de classe, pode-se renomear uma das classes como um "alias:"

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

Agora Vista pode ser usada para referenciar a com.example.real.estate.View e View pode ser usada para referenciar android.view.View dentro do arquivo de layout. Tipos importados podem ser usados como referências de tipo em variáveis e expressões:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>

Observação: O Android Studio ainda não trata importações, portanto o preenchimento automático para variáveis importadas pode não funcionar com o seu IDE. O aplicativo ainda compilará sem problemas e você pode contornar o problema do Ide usando nomes totalmente qualificados nas definições de variáveis.

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Tipos importados também podem ser usados ao referenciar campos estáticos e métodos em expressões:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Como no Java, java.lang.* é importado automaticamente.

Variáveis

Pode-se usar qualquer número de elementos variable dentro do elemento data. Cada elemento variable descreve uma propriedade definível no layout a usar em expressões de vinculação dentro do arquivo de layout.

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

Os tipos de variáveis são inspecionados em tempo de compilação, portanto, se uma variável implementa android.databinding.Observable ou é uma coleção observável, isso deve refletir-se no tipo. Se a variável for uma interface ou classe base que não implementa a interface Observable*, as variáveis não serão observadas!

Quando houver diferentes arquivos de layout para configurações diversas (por exemplo, paisagem ou retrato), as variáveis serão combinadas. Não deve haver definições de variáveis conflitantes entre esses arquivos de layout.

A classe de vinculação gerada deverá ter um configurador e um coletor para cada uma das variáveis descritas. As variáveis usarão os valores Java padrão até que o configurador seja chamado — null para tipos de referência, 0 para int, false para boolean etc.

É gerada uma variável especial chamada context para uso em expressões de vinculação conforme necessário. O valor de context é o Context de getContext() da visualização raiz. A variável context será substituída por uma declaração de variável explícita com aquele nome.

Nomes de classe de vinculação personalizados

Por padrão, uma classe Binding é gerada com base no nome do arquivo de layout, iniciando com letra maiúscula, removendo sublinhados ( _ ), colocando em maiúscula a letra seguinte e adicionando o sufixo "Binding". Essa classe será colocada em um pacote de vinculação de dados sob o pacote do módulo. Por exemplo: o arquivo de layout contact_item.xml gerará ContactItemBinding. Se o pacote do módulo for com.example.my.app, ele será colocado em com.example.my.app.databinding.

As classes Binding podem ser renomeadas ou colocadas em pacotes diferentes ajustando o atributo class do elemento data. Por exemplo:

<data class="ContactItem">
    ...
</data>

Isso gera a classe de vinculação como ContactItem no pacote de vinculação de dados no pacote do módulo. Se a classe deve ser gerada em um pacote diferente dentro do pacote do módulo, ela pode ser prefixada com ".":

<data class=".ContactItem">
    ...
</data>

Nesse caso, ContactItem é gerado diretamente no pacote do módulo. Qualquer pacote pode ser usado se o pacote completo é fornecido:

<data class="com.example.ContactItem">
    ...
</data>

Includes

As variáveis podem ser passadas para uma vinculação do layout incluído do layout que o contém usando o namespace do aplicativo e o nome da variável em um atributo:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

Aqui, deve haver uma variável user nos arquivos de layout name.xml e contact.xml.

A vinculação de dados não suporta include como um filho direto de um elemento de mesclagem. Por exemplo, o seguinte layout não é aceito:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

Linguagem de expressões

Recursos comuns

A linguagem de expressão se parece muito como uma expressão Java. Estas são iguais:

  • Matemática + - / * %
  • Concatenação de strings +
  • Lógica && ||
  • Binária & | ^
  • Unitária + - ! ~
  • Deslocamento >> >>> <<
  • Comparação == > < >= <=
  • instanceof
  • Agrupamento ()
  • Literais — caractere, string, numérico, null
  • Transmissão
  • Chamadas de método
  • Acesso a campos
  • Acesso a matrizes []
  • Operador ternário ?:

Exemplos:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

Operações ausentes

Algumas operações estão ausentes da sintaxe das expressões e que podem ser usadas no Java.

  • this
  • super
  • new
  • Invocação genérica explícita

Operador coalescente nulo

O operador coalescente nulo (??) escolhe o operando esquerdo se não for nulo ou o direito se for nulo.

android:text="@{user.displayName ?? user.lastName}"

Essa é uma funcionalidade equivalente a:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

Referência de propriedades

A primeira já foi discutida em Escrever as primeiras expressões de vinculação de dados acima: referências de JavaBean de forma curta. Quando uma expressão referencia uma propriedade em uma classe, ela usa o mesmo formato para campos, coletores e ObservableFields.

android:text="@{user.lastName}"

Evitar NullPointerException

O código gerado de vinculação de dados automaticamente verifica se há nulos e evita exceções de ponteiro nulo. Por exemplo: na expressão @{user.name}, se user é nulo, user.name recebe o valor padrão (null). Se você estivesse referenciando user.age, onde a idade é um int, o padrão seria 0.

Coleções

Coleções comuns: matrizes, listas, listas esparsas e mapas, podem ser acessados usando o operador [] por conveniência.

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

Literais de string

Ao usar aspas simples em volta do valor do atributo, é fácil usar aspas duplas da expressão:

android:text='@{map["firstName"]}'

Também é possível usar aspas duplas para rodear o valor do atributo. Ao fazer isso, literais de string devem usar aspas simples retas ou aspas simples curvas (`).

android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"

Recursos

É possível acessar recursos como parte de expressões usando a sintaxe normal:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

Strings de formato e plurais podem ser avaliados fornecendo parâmetros:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

Quando um plural usa vários parâmetros, devem-se passar todos os parâmetros:


  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

Alguns recursos exigem avaliação explícita do tipo.

Tipo Referência normal Referência da expressão
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Objetos de dados

Qualquer objeto simples antigo do Java (POJO) pode ser usado para vinculação de dados, mas modificar um POJO não fará com que a IU seja atualizada. O verdadeiro poder da vinculação de dados pode ser usado para dar aos seus objetos de dados a capacidade de notificar quando os dados mudam. Há três mecanismos de notificação de mudança de dados: objetos observáveis, campos observáveis e coleções observáveis.

Quando um desses objetos de dados observáveis é vinculado à IU e uma propriedade do objeto de dados muda, a IU é atualizada automaticamente.

Objetos observáveis

Uma classe que implementa a interface android.databinding.Observable permitirá que a vinculação anexe um único ouvinte a um objeto vinculado para ouvir mudanças em todas as propriedades daquele objeto.

A interface android.databinding.Observable tem um mecanismo para adicionar e remover ouvintes, mas a notificação depende do desenvolvedor. Para tornar o desenvolvimento mais fácil, uma classe de base, android.databinding.BaseObservable, foi criada para implementar o mecanismo de registro do ouvinte. O implementador da classe de dados ainda é responsável por notificar quando as propriedades mudam. Isso é feito atribuindo uma anotação android.databinding.Bindable ao coletor e notificando no configurador.

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

A anotação android.databinding.Bindable gera uma entrada no arquivo de classe BR durante a compilação. O arquivo de classe BR será gerado no pacote do módulo. Se a classe de base para classes de dados não pode ser modificada, pode-se implementar a interface android.databinding.Observable usando-se o android.databinding.PropertyChangeRegistry conveniente para armazenar e notificar os ouvintes de forma eficiente.

ObservableFields

Como a criação de classes android.databinding.Observable é um pouco trabalhosa, os desenvolvedores que desejam poupar tempo ou ter menos propriedades podem usar android.databinding.ObservableField e suas irmãs android.databinding.ObservableBoolean, android.databinding.ObservableByte, android.databinding.ObservableChar, android.databinding.ObservableShort, android.databinding.ObservableInt, android.databinding.ObservableLong, android.databinding.ObservableFloat, android.databinding.ObservableDouble e android.databinding.ObservableParcelable. ObservableFields são objetos observáveis autocontidos de campo único. As versões primitivas evitam encaixotar e desencaixotar durante operações de acesso. Para usar, crie um campo final público na classe de dados:

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

Pronto! Para acessar o valor, use os métodos do acessador set e get:

user.firstName.set("Google");
int age = user.age.get();

Coleções observáveis

Alguns aplicativos usam estruturas mais dinâmicas para reter dados. Coleções observáveis permitem acesso com chaves a esses objetos de dados. android.databinding.ObservableArrayMap é útil quando a chave é um tipo de referência, como String.

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

No layout, pode-se acessar o mapa com chaves de String:

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

android.databinding.ObservableArrayList é útil quando a chave é um inteiro:

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

No layout, a lista pode ser acessada com os índices:

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Vinculação gerada

A classe de vinculação gerada vincula as variáveis de layout às visas dentro do layout. Como discutido anteriormente, o nome e o pacote de Binding podem ser personalizados. Todas as classes de vinculação geradas estendem android.databinding.ViewDataBinding.

Criação

A vinculação deve ser criada logo depois da inflação para garantir que a hierarquia da visualização esteja íntegra antes da vinculação às visualizações com expressões dentro do layout. Há algumas formas de vincular a um layout. A mais comum é usar os métodos estáticos na classe Binding. O método inflate infla a hierarquia da visualização e vincula todos eles em uma etapa. Há uma versão mais simples que só usa um LayoutInflater e outra que também usa um ViewGroup:

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

Se o layout foi inflado usando um mecanismo diferente, ele pode ser vinculado separadamente:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

Algumas vezes, não é possível conhecer a vinculação antecipadamente. Em tais casos, pode-se criar a vinculação com a classe android.databinding.DataBindingUtil:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

Visualizações com IDs

Será gerado uma campo final público para cada visualização com um ID no layout. A vinculação faz uma única passagem na hierarquia de visualizações extraindo as visualizações com IDs. Esse mecanismo pode ser mais rápido do que chamar findViewById para várias visualizações. Por exemplo:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

Gerará uma classe de vinculação com:

public final TextView firstName;
public final TextView lastName;

Os IDs não são tão necessários quanto sem a vinculação de dados, mas ainda há alguns casos em que o acesso a visualizações é necessário no código.

Variáveis

Cada variável receberá métodos de acessador.

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

gerará configuradores e coletores na vinculação:

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

ViewStubs

ViewStubs são um pouco diferentes de visualizações normais. Eles iniciam invisíveis e, quando ficam visíveis ou explicitamente instruídos a inflar, inflam outro layout para se substituírem no layout.

Como o ViewStub essencialmente desaparece da hierarquia de visas, a visualização no objeto de vinculação também deve desaparecer para permitir a coleta. Como as Visualizações são finais, um objeto android.databinding.ViewStubProxy toma o lugar do ViewStub, dando ao desenvolvedor acesso ao ViewStub quando ele existe e acesso à hierarquia de visas infladas quando o ViewStub foi inflado.

Ao inflar outro layout, uma vinculação deve ser estabelecida para o novo layout. Portanto, o ViewStubProxy deve escutar o ViewStub.OnInflateListener do ViewStub e estabelecer a vinculação naquele momento. Como só pode existir um, o ViewStubProxy permite ao desenvolvedor definir nele um OnInflateListener que será chamado depois de estabelecer a vinculação.

Vinculação avançada

Variáveis dinâmicas

Às vezes, a classe de vinculação específica não será conhecida. Por exemplo: um RecyclerView.Adapter que opere em relação a layouts arbitrários não saberá a classe de vinculação específica. Ele ainda deve atribuir o valor de vinculação durante onBindViewHolder(VH, int).

Neste exemplo, todos os layouts que a que a RecyclerView se vincula têm uma variável "item". O BindingHolder tem um método getBinding que retorna a base de android.databinding.ViewDataBinding.

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

Vinculação imediata

Quando uma variável ou um observável muda, a vinculação é agendada para mudar antes do próximo quadro. No entanto, há casos em que a vinculação deve ser executada imediatamente. Para forçar a execução, use o método ViewDataBinding.executePendingBindings().

Encadeamento em segundo plano

É possível mudar o modelo de dados em um encadeamento em segundo plano desde que não seja uma coleção. A vinculação de dados localizará cada variável/campo durante a avaliação para evitar problemas de simultaneidade.

Configuradores de atributos

Sempre que um valor vinculado muda, a classe de vinculação gerada deve chamar um método configurador na visualização com a expressão de vinculação. A estrutura de vinculação de dados tem formas de personalizar que método será chamado para definir o valor.

Configuradores automáticos

Para um atributo, a vinculação de dados tenta encontrar o método setAttribute. O namespace do atributo não importa, somente o nome propriamente dito do atributo.

Por exemplo: uma expressão associada ao atributo android:text de TextView procurará um setText(String). Se a expressão retornar um int, a vinculação de dados procurará um método setText(int). Tenha cuidado para que a expressão retorne o tipo correto, transmitindo-a se necessário. Observe que a vinculação de dados funcionará mesmo se nenhum atributo existir com o nome dado. Portanto, você pode "criar" atributos facilmente para qualquer configurador usando vinculação de dados. Por exemplo, DrawerLayout de apoio não tem nenhum atributo, mas tem muitos configuradores. Você pode usar os configuradores automáticos para usar um desses.

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

Configuradores renomeados

Alguns atributos têm configuradores que não fazem correspondência por nome. Para esses métodos, um atributo pode ser associado ao configurador com a anotação android.databinding.BindingMethods. Deve estar associado a uma classe e contém anotações android.databinding.BindingMethod, uma para cada método renomeado. Por exemplo: o atributo android:tint é associado, na verdade, a setImageTintList(ColorStateList), e não a setTint.

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

É improvável que os desenvolvedores precisem renomear configuradores — os atributos da estrutura do Android já foram implementados.

Configuradores personalizados

Alguns atributos precisam de lógica de vinculação personalizada. Por exemplo, não há configurador associado para o atributo android:paddingLeft. Em vez disso, existe setPadding(left, top, right, bottom). Um método de adaptador de vinculação estático com a anotação android.databinding.BindingAdapter permite que o desenvolvedor personalize a forma de chamar um configurador de um atributo.

Os atributos do Android já têm os BindingAdapters criados. Eis um, por exemplo, para paddingLeft:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

Os adaptadores de vinculação são úteis para outros tipos de personalização. Por exemplo: pode-se chamar um carregador personalizado fora do encadeamento para carregar uma imagem.

Adaptadores de vinculação criados pelo desenvolvedor substituirão os adaptadores padrão de vinculação de dados se houver um conflito.

Você também pode ter adaptadores que recebem vários parâmetros.

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

Esse adaptador será chamado se imageUrl e error forem usados para uma ImageView e se imageUrl for um string e error for um desenhável.

  • Namespaces personalizados são ignorados durante a correspondência.
  • Você também pode compor adaptadores para o namespace do Android.

Métodos de adaptador de vinculação podem opcionalmente usar os valores antigos em seus gerenciadores. Para um método que usa valores antigos e novos, todos os valores antigos dos atributos devem vir primeiro, seguidos dos valores novos:

@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());
   }
}

Gerenciadores de eventos só podem ser usados com interfaces ou classes abstratas com um método abstrato. Por exemplo:

@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);
        }
    }
}

Quando um ouvinte tem vários métodos, é preciso dividi-lo em vários ouvintes. Por exemplo, View.OnAttachStateChangeListener tem dois métodos: onViewAttachedToWindow() e onViewDetachedFromWindow(). Precisamos então criar duas interfaces para diferenciar os atributos e seus gerenciadores.

@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 alteração de um ouvinte também alterará o outro, precisamos ter três adaptadores de vinculação diferentes, um para cada atributo e um para os dois se ambos estiverem definidos.

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final 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);
                    }
                }
            };
        }
        final 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 do que o normal porque a visualização usa adicionar e remover para o ouvinte em vez de um método de definição para View.OnAttachStateChangeListener. A classe android.databinding.adapters.ListenerUtil ajuda a acompanhar os ouvintes anteriores para removê-los 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 ouvinte só deve ser gerado ao executar no Honeycomb MR1 e dispositivos novos, a mesma versão compatível com addOnAttachStateChangeListener(View.OnAttachStateChangeListener).

Conversores

Conversões de objetos

Quando uma expressão de vinculação retornar um objeto, um configurador será escolhido dentre os configuradores automáticos, renomeados e personalizados. O objeto será enviado para um tipo de parâmetro do configurador escolhido.

Essa é uma conveniência para aqueles que usam ObservableMaps para reter dados. Por exemplo:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

O userMap retorna um objeto que será automaticamente transmitido para o tipo de parâmetro encontrado no configurador setText(CharSequence). Quando puder haver confusão sobre o tipo de parâmetro, o desenvolvedor precisará transmitir na expressão.

Conversões personalizadas

Algumas vezes, as conversões devem ser automáticas entre tipos específicos. Por exemplo, ao configurar o segundo plano:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Aqui, o segundo plano obtém um Drawable, mas a cor é um inteiro. Sempre que se espera um Drawable e um inteiro é retornado, deve-se converter o int em um ColorDrawable. Para isso, usa-se um método estático com uma anotação BindingConversion:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

Observe que conversões só ocorrem no nível do configurador, portanto não é permitido misturar tipos desta forma:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Compatibilidade do Android Studio com Data Binding

O Android Studio aceita muitos dos recursos de edição de código para o código de vinculação de dados. Por exemplo, ele aceita os seguintes recursos para expressões de vinculação de dados:

  • Destaque de sintaxe
  • Sinalização de erros de sintaxe da linguagem de expressões
  • Preenchimento de código XML
  • Referências, inclusive navegação (como navegar para uma declaração) e documentação rápida

Observação: Matrizes e um tipo genérico, como a classe android.databinding.Observable, podem exibir erros quando não há erros.

O painel Preview exibe os valores padrão para as expressões de vinculação de dados, se fornecidos. No exemplo a seguir do excerto de um elemento de um arquivo XML de layout, o painel Preview mostra o valor de texto padrão PLACEHOLDER na TextView.

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>

Se for necessário exibir um valor padrão durante a fase de projeto, também é possível usar atributos das ferramentas, em vez dos valores padrão das expressões, como descrito em Atributos de layout em tempo de projeto.