Layouts e expressões de vinculação

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

Os arquivos de layout de vinculação de dados são relativamente diferentes e começam com uma tag raiz de layout, seguida de um elemento data e um elemento raiz view. Esse elemento de visualização é o que sua raiz seria em um arquivo de layout sem vinculação. O código a seguir 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 "@{}". Aqui, 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}" />
    

Objeto de dados

Suponhamos que você tenha um objeto antigo 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 aplicativos terem dados que são lidos uma única vez e nunca mudam depois disso. Também é possível usar um objeto que segue um conjunto de convenções, como os métodos do acessador em Java, conforme mostrado no exemplo a seguir:

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 classe posterior. Como alternativa, também é resolvida com 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 Pascal Case e com o sufixo Binding. O nome do arquivo de layout acima é activity_main.xml. Portanto, a classe gerada correspondente é ActivityMainBinding. Essa classe contém todas as vinculações das propriedades de layout (por exemplo, a variável user) até as visualizações do layout, e sabe como atribuir valores para as expressões de vinculação. O método recomendado para criar as vinculações é que isso seja feito enquanto o layout é inflado, conforme mostrado no exemplo a seguir:

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 de execução, o app exibe o usuário Test na IU. Como alternativa, é possível receber 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, pode ser preferível 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ógica: && ||
  • Binário: & | ^
  • Unários: + - ! ~
  • Shift: >> >>> <<
  • Comparação == > < >= <= (< precisa ter escape como &lt;)
  • instanceof
  • Agrupamento: ()
  • Literais: caractere, string, numérico, null
  • Cast
  • Chamadas de método
  • Acesso ao campo
  • Acesso à matriz: []
  • 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

As operações a seguir estão ausentes na sintaxe de expressões que pode ser usada 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 caso não seja null ou à direita caso o anterior seja null.

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

Funcionalmente, isso é equivalente a:

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 é 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 0.

Ver referências

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

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

No exemplo a seguir, a visualização TextView referencia 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}"/>
    

Coleções

Por conveniência, coleções comuns, como matrizes, listas, listas esparsas e mapas, podem ser acessadas usando o operador [].

<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, @{map[key]} no exemplo acima pode ser substituído por @{map.key}.

Literais de strings

É possível usar aspas simples ao redor do valor do atributo, o que permite usar aspas duplas na expressão, conforme mostrado no exemplo a seguir:

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

Também é possível usar aspas duplas ao redor do valor do atributo. Ao fazer isso, os literais de string precisam ser delimitados por acentos graves `:

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, é necessário transmitir todos os parâmetros:


      Have an orange
      Have %d oranges

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

Alguns recursos exigem avaliação de tipo explícito, 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 possibilita escrever expressões que manipulam eventos enviados das visualizações (por exemplo, o método onClick()). Os nomes de 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(). Portanto, 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. Você pode usar os atributos a seguir 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 os mecanismos a seguir para manipular 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 a método, a vinculação de dados une a referência de método e o objeto de propriedade 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 e, em vez disso, definirá um listener null.
  • Vinculações de listener: são expressões lambda avaliadas quando o evento ocorre. 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

Os eventos podem ser vinculados diretamente aos métodos do gerenciador, da mesma forma que android:onClick pode ser atribuído a um método em uma atividade. Uma grande vantagem em relação ao atributo onClick de View é que a expressão é processada no momento de 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 a métodos e vinculações do listener é que a implementação real do listener é criada quando os dados são vinculados, e não quando o evento é acionado. Caso prefira avaliar a expressão quando o evento ocorrer, use a vinculação do 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. Por exemplo, considere o seguinte 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 a métodos, 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 versão 2.0 e posteriores.

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

Kotlin

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

    

Java

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

    

Em seguida, vincule 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. Como nas expressões de vinculação normais, você ainda recebe o valor nulo e a segurança da linha de execução da vinculação de dados enquanto essas expressões de listener são avaliadas.

No exemplo acima, não definimos o parâmetro view que é passado para onClick(View). As vinculações do listener oferecem duas opções para os parâmetros do listener: ignorar todos os parâmetros do método ou nomear todos eles. Caso prefira nomear os parâmetros, você pode usá-los na expressão. Por exemplo, a expressão acima poderia ser escrita da seguinte forma:

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

Ou, caso queira usar o parâmetro na expressão, ele poderia funcionar da seguinte maneira:

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)}"
    

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 retorna um valor de tipo diferente de void, suas expressões também precisam retornar o mesmo tipo de valor. Por exemplo, se você quer ouvir o evento de clique longo, sua expressão precisa 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 a expressão não puder ser avaliada devido a objetos null, a vinculação de dados retornará o valor padrão para esse tipo. Por exemplo, null para tipos de referência, 0 para int, false para boolean etc.

Caso você precise usar uma expressão com um predicado (por exemplo, ternário), é possível usar void como símbolo.

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

Evitar listeners complexos

As expressões de listeners são muito poderosas e podem tornar seu código muito fácil de ler. Por outro lado, listeners que contêm expressões complexas dificultam a leitura e a manutenção dos seus layouts. Essas expressões precisam ser simples, como passar dados disponíveis da IU para seu método de callback. É necessário implementar toda lógica de negócios dentro do método de callback invocado a partir da expressão de listener.

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

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

Importações

As importações permitem que você referencie facilmente classes dentro do seu arquivo de layout, assim como no código gerenciado. É possível 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>
    

Importar a classe View permite referenciá-la a partir das 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

Em casos em que há conflitos de nome de classe, uma das classes pode ser renomeada como um alias. O exemplo a seguir 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 dentro do arquivo de layout.

Importar outras classes

Os tipos importados podem ser usados 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ê também 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"/>
    

Os tipos importados também podem ser usados ao referenciar campos e métodos estáticos em expressões. O código a seguir importa a classe MyStringUtils e referencia o 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 e 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 de compilação. Portanto, se uma variável implementa Observable ou é uma coleção observável, isso precisa 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 houver arquivos de layout diferentes para várias configurações (por exemplo, paisagem ou retrato), as variáveis serão combinadas. Provavelmente, não 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 se apropriam dos 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

As variáveis podem ser passadas para a vinculação de um layout incluído a partir do layout contido, usando o namespace do app e o nome da variável em um atributo. O exemplo a seguir mostra 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 é compatível com inclusões como filhas diretas 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 vinculação de dados, consulte os seguintes recursos adicionais.

Amostras

Codelabs

Postagens do blog