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<
) 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<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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 listenernull
. - 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:
</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: -> 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: -> 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: -> 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: 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: -> 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: -> 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<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.