Layouts e expressões de vinculação

A linguagem de expressões permite escrever expressões que processam eventos enviados por visualizações. A biblioteca Data Binding gera automaticamente as classes necessárias para vincular as visualizações do layout aos objetos de dados.

Os arquivos de layout de vinculação de dados são um pouco diferentes e começam com uma tag raiz de layout, seguida por um elemento data e um elemento raiz view. Esse elemento de visualização é o que sua raiz é em um arquivo de layout sem vinculação. O código abaixo mostra um exemplo de arquivo de layout.

<?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 variável user em data descreve uma propriedade que pode ser usada nesse layout:

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

As expressões no layout são escritas nas propriedades do atributo usando a sintaxe @{}. No exemplo a seguir, o texto TextView é definido como a propriedade firstName da variável user:

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

Objetos de dados

Suponha que você tenha um objeto simples para descrever a entidade User:

Kotlin

data class User(val firstName: String, val lastName: String)

Java


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 que os apps tenham dados que são lidos uma vez e nunca mudam depois disso. Também é possível usar um objeto que segue um conjunto de convenções, como o uso de métodos do acessador na linguagem de programação Java, conforme mostrado neste exemplo:

Kotlin

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

Java

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 acessa o campo firstName na classe anterior e o método getFirstName() na última classe. Ele também é resolvido como firstName(), se esse método existir.

Vincular dados

Uma classe de vinculação é gerada para cada arquivo de layout. Por padrão, o nome da classe é baseado no nome do arquivo de layout, convertido para o padrão Pascal, com o sufixo Binding adicionado a ele. Por exemplo, o nome do arquivo do layout anterior é activity_main.xml, portanto, a classe de vinculação correspondente gerada é ActivityMainBinding.

Essa classe contém todas as vinculações das propriedades de layout (por exemplo, a variável user) para as visualizações do layout e sabe como atribuir valores para as expressões de vinculação. Recomendamos criar as vinculações ao inflar o layout, conforme mostrado neste exemplo:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

Java

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

No momento da execução, o app mostra o usuário de teste na interface. Você também pode acessar a visualização usando um LayoutInflater, conforme mostrado no exemplo a seguir:

Kotlin

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

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

Se você estiver usando itens de vinculação de dados dentro de um adaptador Fragment, ListView ou RecyclerView, talvez prefira usar os métodos inflate() das classes de vinculação ou a classe DataBindingUtil, conforme mostrado no exemplo de código a seguir.

Kotlin

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

Java

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

Linguagem de expressões

Recursos comuns

A linguagem de expressões é muito parecida com as expressões encontradas no código gerenciado. É possível usar os seguintes operadores e palavras-chave na linguagem de expressões:

  • matemáticos: + - / * %
  • Concatenação de strings: +
  • Lógico: && ||
  • Binário: & | ^
  • Unário: + - ! ~
  • Shift: >> >>> <<
  • Comparação: == > < >= <= (o < precisa ter o escape de &lt;).
  • instanceof
  • Agrupamento: ()
  • Literais, como caractere, string, numérico, null
  • Transmissão
  • Chamadas de método
  • Acesso ao campo
  • Acesso à matriz: []
  • Operador ternário: ?:

Veja alguns exemplos:

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

Operações ausentes

As seguintes operações estão ausentes na sintaxe de expressão, que é possível usar no código gerenciado:

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

Operador de coalescência nulo

O operador de coalescência nulo (??) escolhe o operando à esquerda se não for null ou à direita se o primeiro for null:

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

Isso é funcionalmente equivalente ao seguinte:

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

Referências de propriedades

Uma expressão pode referenciar uma propriedade em uma classe usando o formato a seguir, que é o mesmo para campos, getters e objetos ObservableField:

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

Evitar exceções de ponteiro nulo

O código de vinculação de dados gerado verifica automaticamente se há valores null e evita exceções de ponteiro nulo. Por exemplo, na expressão @{user.name}, se user for nulo, o valor padrão null será atribuído a user.name. Se você referenciar user.age, em que a idade é do tipo int, a vinculação de dados usará o valor padrão de 0.

Ver referências

Uma expressão pode referenciar outras visualizações no layout por ID, usando a seguinte sintaxe:

android:text="@{exampleText.text}"

No exemplo abaixo, a visualização TextView faz referência a uma visualização EditText no mesmo layout:

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>

Coletâneas

Acesse coleções comuns, como matrizes, listas, listas esparsas e mapas, 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>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"

Você também pode se referir a um valor no mapa usando a notação object.key. Por exemplo, você pode substituir @{map[key]} no exemplo anterior por @{map.key}.

Literais de strings

Você pode usar aspas simples para cercar o valor do atributo, o que permite o uso de aspas duplas na expressão, conforme mostrado no exemplo a seguir:

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

Você também pode colocar o valor do atributo entre aspas duplas. Ao fazer isso, os literais de string precisam ser cercados por acentos graves `, conforme mostrado aqui:

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

Recursos

Uma expressão pode referenciar recursos do app com a seguinte sintaxe:

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

As strings de formato e plurais podem ser avaliadas fornecendo parâmetros:

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

Você pode transmitir referências de propriedade e referências de visualização como parâmetros de recursos:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

Quando um plural assume vários parâmetros, transmita 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, conforme mostrado na tabela a seguir:

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

Manipular eventos

A vinculação de dados permite que você escreva expressões que processam eventos enviados das visualizações, por exemplo, o método onClick(). Os nomes dos atributos de eventos são determinados pelo nome do método listener, com algumas exceções. Por exemplo, View.OnClickListener tem um método onClick(), então o atributo para esse evento é android:onClick.

Existem alguns manipuladores de eventos especializados para o evento de clique que precisam de um atributo diferente de android:onClick para evitar conflito. Use os atributos abaixo para evitar esse tipo de conflito:

Classe. Listener setter Atributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Você pode usar esses dois mecanismos, descritos em detalhes nas seções a seguir, para processar um evento:

  • Referências a métodos: nas suas expressões, você pode referenciar métodos que estejam em conformidade com a assinatura do método listener. Quando uma expressão é avaliada como uma referência de método, a vinculação de dados une a referência do método e o objeto do proprietário em um listener e define esse listener na visualização de destino. Se a expressão for avaliada como null, a vinculação de dados não criará um listener, mas definirá um listener null.
  • Vinculações de listener: são expressões lambda que são avaliadas quando o evento acontece. A vinculação de dados sempre cria um listener, que é definido na visualização. Quando o evento é enviado, o listener avalia a expressão lambda.

Referências a métodos

É possível vincular eventos a métodos de gerenciador diretamente, da mesma forma que é possível atribuir android:onClick a um método em uma atividade. Uma vantagem em comparação com o atributo View onClick é que a expressão é processada durante a compilação. Portanto, se o método não existir ou a assinatura dele estiver incorreta, você receberá um erro de tempo de compilação.

A principal diferença entre as referências de método e as vinculações de listener é que a implementação real do listener é criada quando os dados são vinculados, não quando o evento é acionado. Se você preferir avaliar a expressão quando o evento acontecer, use vinculações de listener.

Para atribuir um evento ao gerenciador, use uma expressão de vinculação normal, com o valor sendo o nome do método a ser chamado. Confira este exemplo de objeto de dados de layout:

Kotlin

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

Java

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

A expressão de vinculação pode atribuir o listener de clique de uma visualização ao método onClickFriend() da seguinte maneira:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <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>

Vinculações de listener

As vinculações de listener são expressões de vinculação executadas quando um evento acontece. Elas são semelhantes às referências de método, mas permitem que você execute expressões de vinculação de dados arbitrárias. Esse recurso está disponível com o Plug-in do Android para Gradle para Gradle versão 2.0 e mais recentes.

Nas referências de método, os parâmetros do método precisam corresponder aos parâmetros do listener de eventos. Nas vinculações de listener, apenas o valor de retorno precisa corresponder ao valor de retorno esperado do listener, a menos que esteja esperando void. Por exemplo, considere a seguinte classe de apresentador que tem um método onSaveClick():

Kotlin

class Presenter {
    fun onSaveClick(task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(Task task){}
}

Você pode vincular o evento de clique ao método onSaveClick() da seguinte maneira:

<?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>

Quando um callback é usado em uma expressão, a vinculação de dados cria automaticamente o listener necessário e o registra para o evento. Quando a visualização dispara o evento, a vinculação de dados avalia a expressão especificada. Assim como nas expressões de vinculação regulares, você recebe o valor nulo e a segurança da linha de execução da vinculação de dados enquanto essas expressões de listener estão sendo avaliadas.

No exemplo anterior, o parâmetro view que é transmitido para onClick(View) não está definido. As vinculações de listener oferecem duas opções para parâmetros de listener: você pode ignorar todos os parâmetros do método ou nomear todos eles. Se preferir nomear os parâmetros, use-os na expressão. Por exemplo, é possível escrever a expressão anterior da seguinte maneira:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

Se você quiser usar o parâmetro na expressão, faça o seguinte:

Kotlin

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

E você pode usar uma expressão lambda com mais de um parâmetro:

Kotlin

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

Java

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ê está ouvindo retornar um valor de tipo diferente de void, suas expressões também precisarão retornar o mesmo tipo de valor. Por exemplo, se você quiser detectar o evento de tocar e pressionar (clique longo), sua expressão precisará retornar um booleano.

Kotlin

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Java

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

Se não for possível avaliar a expressão devido a objetos null, a vinculação de dados retornará o valor padrão desse tipo, como null para tipos de referência, 0 para int ou false para boolean.

Se você precisar usar uma expressão com um predicado, por exemplo, um ternário, poderá usar void como símbolo:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

Evitar listeners complexos

As expressões de listener são poderosas e podem facilitar a leitura do código. Por outro lado, os listeners que contêm expressões complexas tornam os layouts mais difíceis de ler e manter. Mantenha suas expressões tão simples quanto transmitir os dados disponíveis da sua IU para o método de callback. Implemente qualquer lógica de negócios dentro do método de callback que você invocar a partir da expressão do listener.

Importações, variáveis e inclusões

A Data Binding Library fornece recursos como importações, variáveis e inclusões. As importações facilitam a referência de classes dentro dos arquivos de layout. As variáveis permitem descrever uma propriedade que pode ser usada em expressões de vinculação. As inclusões permitem reutilizar layouts complexos no seu app.

Importações

As importações permitem referenciar classes dentro do arquivo de layout, como em código gerenciado. Você pode usar zero ou mais elementos import dentro do elemento data. O exemplo de código a seguir importa a classe View para o arquivo de layout:

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

A importação da classe View permite referenciá-la a partir das suas expressões de vinculação. O exemplo a seguir mostra como referenciar as constantes VISIBLE e GONE da classe View:

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

Aliases de tipos

Quando há conflitos de nome de classe, é possível renomear uma das classes como um alias. O exemplo abaixo renomeia a classe View no pacote com.example.real.estate para Vista:

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

Você pode usar Vista para referenciar com.example.real.estate.View e View para referenciar android.view.View no arquivo de layout.

Importar outras classes

Você pode usar tipos importados como referências de tipo em variáveis e expressões. O exemplo a seguir mostra User e List usados como o tipo de uma variável:

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

Você pode usar os tipos importados para transmitir parte de uma expressão. O exemplo a seguir transmite a propriedade connection para um tipo de User:

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

Também é possível usar tipos importados ao referenciar campos e métodos estáticos em expressões. O código a seguir importa a classe MyStringUtils e faz referência ao método capitalize:

<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"/>

Assim como no código gerenciado, java.lang.* é importado automaticamente.

Variáveis

É possível usar vários elementos variable dentro do elemento data. Cada elemento variable descreve uma propriedade que pode ser definida no layout a ser usada em expressões de vinculação dentro do arquivo de layout. O exemplo a seguir declara as variáveis user, image e note:

<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 no momento da compilação. Portanto, se uma variável implementar Observable ou for uma coleção observável, isso precisará ser refletido no tipo. Se a variável for uma classe ou interface base que não implementa a interface Observable, as variáveis não serão observadas.

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

A classe de vinculação gerada tem um setter e um getter para cada uma das variáveis descritas. As variáveis usam os valores padrão de código gerenciado até que o setter seja chamado: null para tipos de referência, 0 para int, false para boolean etc.

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

Inclui

Você pode transmitir variáveis para a vinculação de um layout incluído a partir do layout que a contém usando o namespace do app e o nome da variável em um atributo. O exemplo a seguir mostra as variáveis user incluídas dos arquivos de layout name.xml e contact.xml:

<?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>

A vinculação de dados não suporta uma inclusão como filha direta de um elemento de combinação. Por exemplo, o layout a seguir não é compatível:

<?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><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

Outros recursos

Para saber mais sobre a vinculação de dados, consulte os recursos adicionais a seguir.

Exemplos

Codelabs

Postagens do blog