Layout ed espressioni di associazione

Il linguaggio di espressione consente di scrivere espressioni che gestiscono gli eventi inviati dalle viste. La libreria Data Binding genera automaticamente le classi necessarie per associare le viste nel layout agli oggetti dati.

I file di layout di associazione di dati sono leggermente diversi e iniziano con un tag principale di layout, seguito da un elemento data e un elemento principale view. Questo elemento di visualizzazione è l'elemento principale di un file di layout non vincolante. Il seguente codice mostra un file di layout di esempio:

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

La variabile user in data descrive una proprietà che può essere utilizzata in questo layout:

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

Le espressioni all'interno del layout sono scritte nelle proprietà degli attributi utilizzando la sintassi @{}. Nell'esempio seguente, il testo TextView è impostato sulla proprietà firstName della variabile user:

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

Oggetti dati

Supponi di avere un oggetto semplice per descrivere l'entità 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;
  }
}

Questo tipo di oggetto contiene dati che non cambiano mai. È frequente nelle app avere dati che vengono letti una sola volta e non cambiano in seguito. È inoltre possibile utilizzare un oggetto che segue una serie di convenzioni, ad esempio l'utilizzo dei metodi della funzione di accesso nel linguaggio di programmazione Java, come mostrato nell'esempio seguente:

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

Dal punto di vista dell'associazione di dati, queste due classi sono equivalenti. L'espressione @{user.firstName} utilizzata per l'attributo android:text accede al campo firstName nella classe precedente e al metodo getFirstName() nella seconda. Viene inoltre risolto in firstName(), se esiste.

Associazione dei dati

Per ogni file di layout viene generata una classe di associazione. Per impostazione predefinita, il nome della classe si basa sul nome del file di layout, convertito in lettere maiuscole e minuscole con l'aggiunta del suffisso Binding. Ad esempio, il nome file del layout precedente è activity_main.xml, quindi la classe di associazione generata corrispondente è ActivityMainBinding.

Questa classe contiene tutte le associazioni dalle proprietà del layout, ad esempio la variabile user, alle viste del layout e sa come assegnare i valori per le espressioni di associazione. Ti consigliamo di creare le associazioni durante l'aumento artificiale del layout, come mostrato nell'esempio seguente:

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

In fase di runtime, l'app mostra l'utente di test nella UI. In alternativa, puoi ottenere la visualizzazione utilizzando un elemento LayoutInflater, come mostrato nell'esempio seguente:

Kotlin

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

Java

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

Se utilizzi elementi di associazione di dati all'interno di un adattatore Fragment, ListView o RecyclerView, ti consigliamo di utilizzare i metodi inflate() delle classi di associazioni o la classe DataBindingUtil, come mostrato nell'esempio di codice riportato di seguito:

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

Linguaggio di espressione

Funzionalità comuni

Il linguaggio delle espressioni è molto simile alle espressioni trovate nel codice gestito. Puoi utilizzare i seguenti operatori e parole chiave nel linguaggio di espressione:

  • Matematica: + - / * %
  • Concatenazione di stringhe: +
  • Logico: && ||
  • Binario: & | ^
  • Unaria: + - ! ~
  • Maiusc: >> >>> <<
  • Confronto: == > < >= <= (< deve essere preceduto da una sequenza di escape come &lt;)
  • instanceof
  • Raggruppamento: ()
  • Valori letterali, ad esempio carattere, stringa, numerico, null
  • Trasmissione
  • Chiamate al metodo
  • Accesso ai campi
  • Accesso array: []
  • Operatore ternario: ?:

Ecco alcuni esempi:

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

Operazioni mancanti

Le seguenti operazioni non sono presenti nella sintassi dell'espressione che puoi utilizzare nel codice gestito:

  • this
  • super
  • new
  • Chiamata generica esplicita

Operatore di coalescenza nullo

L'operatore di coalescenza null (??) sceglie l'operando sinistro se non è null o quello destro se il primo è null:

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

Questa funzionalità equivale a quanto segue:

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

Riferimenti proprietà

Un'espressione può fare riferimento a una proprietà in una classe utilizzando il seguente formato, che è lo stesso per campi, getter e oggetti ObservableField:

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

Evita eccezioni puntatore null

Il codice di associazione di dati generato controlla automaticamente la presenza di valori null ed evita eccezioni di puntatori nulli. Ad esempio, nell'espressione @{user.name}, se user è null, a user.name viene assegnato il suo valore predefinito null. Se fai riferimento a user.age, dove l'età è di tipo int, l'associazione di dati utilizza il valore predefinito di 0.

Visualizza riferimenti

Un'espressione può fare riferimento ad altre viste nel layout in base all'ID, utilizzando la seguente sintassi:

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

Nell'esempio seguente, la vista TextView fa riferimento a una vista EditText nello stesso 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}"/>

Raccolte

Puoi accedere a raccolte comuni, come array, elenchi, elenchi sparsi e mappe, utilizzando l'operatore [] per praticità.

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

Puoi anche fare riferimento a un valore nella mappa utilizzando la notazione object.key. Ad esempio, puoi sostituire @{map[key]} nell'esempio precedente con @{map.key}.

Valori letterali stringa

Puoi racchiudere il valore dell'attributo tra virgolette singole, in modo da poter usare virgolette doppie nell'espressione, come mostrato nell'esempio seguente:

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

Puoi anche racchiudere il valore dell'attributo tra virgolette. In questo caso, i valori letterali stringa devono essere racchiusi tra apici inversi `, come mostrato qui:

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

Risorse

Un'espressione può fare riferimento a risorse dell'app con la seguente sintassi:

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

Puoi valutare le stringhe di formato e i plurali fornendo i parametri:

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

Puoi passare riferimenti alle proprietà e riferimenti visualizzazione come parametri delle risorse:

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

Quando il plurale utilizza più parametri, trasmettili tutti:


  Have an orange
  Have %d oranges

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

Alcune risorse richiedono una valutazione esplicita del tipo, come mostrato nella seguente tabella:

Tipo Riferimento normale Riferimento espressione
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Gestione degli eventi

L'associazione di dati consente di scrivere eventi di gestione delle espressioni che vengono inviati dalle viste, ad esempio il metodo onClick(). I nomi degli attributi evento sono determinati dal nome del metodo listener, con alcune eccezioni. Ad esempio, View.OnClickListener ha un metodo onClick(), quindi l'attributo di questo evento è android:onClick.

Esistono alcuni gestori di eventi specializzati per l'evento click che necessitano di un attributo diverso da android:onClick per evitare un conflitto. Puoi utilizzare i seguenti attributi per evitare questi tipi di conflitti:

Classe Impostatore ascoltatori Attributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Puoi utilizzare questi due meccanismi, descritti dettagliatamente nelle sezioni che seguono, per gestire un evento:

  • Riferimenti al metodo: nelle tue espressioni, puoi fare riferimento a metodi conformi alla firma del metodo listener. Quando un'espressione valuta un riferimento al metodo, l'associazione di dati inserisce il riferimento al metodo e l'oggetto proprietario in un listener e lo imposta nella vista di destinazione. Se l'espressione restituisce null, l'associazione di dati non crea un listener e imposta invece un listener null.
  • Associazioni di listener: sono espressioni lambda che vengono valutate quando si verifica l'evento. L'associazione di dati crea sempre un ascoltatore, che imposta nella vista. Quando viene inviato l'evento, l'ascoltatore valuta l'espressione lambda.

Riferimenti al metodo

Puoi associare direttamente gli eventi ai metodi del gestore, in modo simile all'assegnazione di android:onClick a un metodo in un'attività. Un vantaggio rispetto all'attributo onClick View è che l'espressione viene elaborata in fase di compilazione. Quindi, se il metodo non esiste o la sua firma non è corretta, riceverai un errore in fase di compilazione.

La principale differenza tra i riferimenti al metodo e le associazioni dei listener è che l'implementazione effettiva del listener viene creata quando vengono associati i dati, non quando viene attivato l'evento. Se preferisci valutare l'espressione quando si verifica l'evento, utilizza le associazioni di listener.

Per assegnare un evento al rispettivo gestore, utilizza un'espressione di associazione normale, dove il valore è il nome del metodo da chiamare. Prendiamo come esempio il seguente oggetto dati di layout:

Kotlin

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

Java

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

L'espressione di associazione può assegnare il listener dei clic per una vista al metodo onClickFriend(), come indicato di seguito:

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

Associazioni di listener

Le associazioni di listener sono espressioni di associazione che vengono eseguite quando si verifica un evento. Sono simili ai riferimenti ai metodi, ma consentono di eseguire espressioni di associazione di dati arbitrari. Questa funzionalità è disponibile con il plug-in Android per Gradle versione 2.0 e successive.

Nei riferimenti al metodo, i parametri del metodo devono corrispondere a quelli del listener di eventi. Nelle associazioni listener, solo il valore restituito deve corrispondere al valore restituito previsto del listener, a meno che non sia previsto void. Ad esempio, considera la seguente classe presentatore che ha un metodo onSaveClick():

Kotlin

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

Java

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

Puoi associare l'evento di clic al metodo onSaveClick() nel seguente modo:

<?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 viene utilizzato un callback in un'espressione, l'associazione di dati crea automaticamente il listener necessario e lo registra per l'evento. Quando la vista attiva l'evento, l'associazione di dati valuta l'espressione specificata. Come per le espressioni di associazione regolari, ottieni la sicurezza null e thread dell'associazione di dati durante la valutazione di queste espressioni di ascolto.

Nell'esempio precedente, il parametro view passato a onClick(View) non è stato definito. Le associazioni di listener offrono due scelte per i parametri listener: puoi ignorare tutti i parametri del metodo o assegnare un nome a tutti. Se preferisci assegnare un nome ai parametri, puoi utilizzarli nell'espressione. Ad esempio, puoi scrivere l'espressione precedente come segue:

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

Se vuoi utilizzare il parametro nell'espressione, puoi farlo come segue:

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

Inoltre, puoi utilizzare un'espressione lambda con più di un parametro:

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 l'evento che stai ascoltando restituisce un valore il cui tipo non è void, anche le tue espressioni devono restituire lo stesso tipo di valore. Ad esempio, se vuoi rimanere in ascolto dell'evento tocco e pressione (clic lungo), l'espressione deve restituire un valore 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 l'espressione non può essere valutata a causa di null oggetti, l'associazione di dati restituisce il valore predefinito per quel tipo, ad esempio null per i tipi di riferimento, 0 per int o false per boolean.

Se devi utilizzare un'espressione con un predicato, ad esempio un ternario, puoi utilizzare void come simbolo:

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

Evita listener complessi

Le espressioni listener sono potenti e possono facilitare la lettura del codice. D'altra parte, i listener che contengono espressioni complesse rendono i tuoi layout più difficili da leggere e gestire. Mantieni le tue espressioni semplici, come il passaggio dei dati disponibili dalla UI al tuo metodo di callback. Implementa qualsiasi logica di business all'interno del metodo di callback che richiami dall'espressione listener.

Importazioni, variabili e include

La libreria di associazione dei dati fornisce funzionalità come importazioni, variabili e include. Le importazioni rendono le classi di facile riferimento all'interno dei file di layout. Le variabili consentono di descrivere una proprietà che può essere utilizzata nelle espressioni di associazione. Ti consentono di riutilizzare layout complessi in tutta l'app.

Importazioni

Le importazioni ti consentono di fare riferimento alle classi all'interno del file di layout, come nel codice gestito. Puoi utilizzare zero o più elementi import all'interno dell'elemento data. Il seguente esempio di codice importa la classe View nel file di layout:

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

L'importazione della classe View consente di farvi riferimento dalle espressioni di associazione. L'esempio seguente mostra come fare riferimento alle costanti VISIBLE e GONE della 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}"/>

Alias del tipo

In caso di conflitti di nomi di classe, puoi rinominare una delle classi con un alias. L'esempio seguente rinomina la classe View nel pacchetto com.example.real.estate in Vista:

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

Puoi quindi utilizzare Vista per fare riferimento a com.example.real.estate.View e View per fare riferimento a android.view.View nel file di layout.

Importa altri corsi

Puoi utilizzare i tipi importati come riferimenti ai tipi in variabili ed espressioni. L'esempio seguente mostra User e List utilizzati come tipo di variabile:

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

Puoi utilizzare i tipi importati per trasmettere parte di un'espressione. Il seguente esempio trasmette la proprietà connection a un tipo di User:

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

Puoi utilizzare i tipi importati anche quando fai riferimento a metodi e campi statici nelle espressioni. Il seguente codice importa la classe MyStringUtils e fa riferimento al suo metodo 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"/>

Proprio come nel codice gestito, java.lang.* viene importato automaticamente.

Variabili

Puoi utilizzare più elementi variable all'interno dell'elemento data. Ogni elemento variable descrive una proprietà che può essere impostata sul layout da utilizzare nelle espressioni di associazione all'interno del file di layout. Nell'esempio seguente vengono dichiarate le variabili 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>

I tipi di variabili vengono ispezionati al momento della compilazione, quindi se una variabile implementa Observable o è una raccolta osservabile, che deve essere riflessa nel tipo. Se la variabile è una classe di base o un'interfaccia che non implementa l'interfaccia Observable, le variabili non vengono osservate.

Quando sono presenti file di layout diversi per varie configurazioni (ad esempio orizzontale o verticale), le variabili vengono combinate. Non devono esistere definizioni delle variabili in conflitto tra questi file di layout.

La classe di associazione generata ha un setter e un getter per ciascuna delle variabili descritte. Le variabili assumono i valori predefiniti del codice gestito finché non viene chiamato il setter: null per i tipi di riferimento, 0 per int, false per boolean e così via.

Viene generata una variabile speciale denominata context da utilizzare nelle espressioni di associazione secondo necessità. Il valore di context è l'oggetto Context del metodo getContext() della vista principale. La variabile context è sovrascritta da una dichiarazione di variabile esplicita con questo nome.

Comprende

Puoi trasferire variabili nell'associazione di un layout incluso dal layout contenente utilizzando lo spazio dei nomi dell'app e il nome della variabile in un attributo. L'esempio seguente mostra le variabili user incluse nei file di 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>

L'associazione di dati non supporta l'inclusione come elemento secondario diretto di un elemento di unione. Ad esempio, il seguente layout non è supportato:

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

Risorse aggiuntive

Per scoprire di più sull'associazione di dati, consulta le seguenti risorse aggiuntive.

Samples

Codelab

Post del blog