Layout ed espressioni di associazione

Il linguaggio delle espressioni consente di scrivere espressioni per gestire gli eventi inviati in base alle visualizzazioni. La libreria di associazioni dati genera automaticamente le classi richieste per associare le viste nel layout agli oggetti dati.

I file di layout dell'associazione di dati sono leggermente diversi e iniziano con un tag principale di layout, seguito da un elemento data e da un elemento principale view. Questa visualizzazione corrisponde all'elemento principale di un file di layout non vincolante. Il seguente codice mostra un esempio di file di 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>

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 vengono scritte nelle proprietà degli attributi utilizzando Sintassi @{}. Nell'esempio seguente, Il testo di TextView è impostato sul 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 normale 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 include dati che non cambiano mai. È comune nelle app avere che vengono letti una volta sola e non cambiano più in seguito. È anche possibile usare un oggetto che segue un insieme di convenzioni, come l'utilizzo dei metodi della funzione di accesso il linguaggio di programmazione Java, come illustrato 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. La l'espressione @{user.firstName} utilizzata per android:text accede al campo firstName nella classe precedente e getFirstName() nella seconda classe. Viene inoltre risolta firstName(), se questo metodo esiste.

Associazione dei dati

Viene generata una classe di associazione per ogni file di layout. Per impostazione predefinita, il nome si basa sul nome del file di layout, convertito in formato Pascal, con il suffisso Binding aggiunto. 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 delle proprietà del layout, ad esempio la variabile user alle visualizzazioni del layout e sa come assegnare i valori le espressioni di associazione. Consigliamo di creare le associazioni durante il gonfiaggio il 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 LayoutInflater, come mostrato in 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 oggetto Fragment, ListView oppure RecyclerView potresti preferire l'utilizzo dell'adattatore inflate() delle classi di associazioni o DataBindingUtil, come nell'esempio di codice che segue:

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 assomiglia molto alle espressioni che si trovano nel codice gestito. Tu puoi utilizzare i seguenti operatori e parole chiave nel linguaggio delle espressioni:

  • Matematica: + - / * %
  • Concatenazione di stringhe: +
  • Logico: && ||
  • Elemento binario: & | ^
  • Unaaria: + - ! ~
  • Maiusc: >> >>> <<
  • Confronto: == > < >= <= (< deve essere sottoposto a escape come &lt;)
  • instanceof
  • Raggruppamento: ()
  • Valori letterali, ad esempio carattere, stringa, numerico, null
  • Cast
  • Chiamate al metodo
  • Accesso ai campi
  • Accesso all'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 delle espressioni che puoi utilizzare nel codice gestito:

  • this
  • super
  • new
  • Chiamata generica esplicita

Operatore di coalescenza null

L'operatore di coalescenza null (??) sceglie l'operando sinistro se non è null o quella a destra se la prima è null:

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

Questo è funzionalmente equivalente a quanto segue:

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

Riferimenti proprietà

Un'espressione può fare riferimento a una proprietà di una classe utilizzando il formato seguente: che è la stessa per campi, getter ObservableField oggetti:

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

Evita le eccezioni relative al puntatore nullo

Il codice di associazione dei dati generati verifica automaticamente i valori null ed evita tranne eccezioni del puntatore nullo. Ad esempio, nell'espressione @{user.name}, se user è nullo. A user.name viene assegnato il valore predefinito null. Se riferimento 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 quanto segue: sintassi:

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

Nell'esempio seguente, la vista TextView fa riferimento a una vista EditText in lo 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 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. Per 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 utilizzare virgolette doppie nell'espressione, come illustrato nell'esempio seguente:

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

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

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

Risorse

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

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

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

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

Puoi trasmettere riferimenti di proprietà e visualizzazione riferimenti come parametri delle risorse:

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

Quando una forma plurale accetta più parametri, passa tutti i parametri:


  Have an orange
  Have %d oranges

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

Alcune risorse richiedono una valutazione del tipo esplicita, come mostrato di seguito 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 gli eventi di gestione delle espressioni che vengono inviati da le viste, ad esempio 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 per questo evento è android:onClick.

Esistono alcuni gestori di eventi specializzati per l'evento click che richiedono una diverso da android:onClick per evitare conflitti. Puoi utilizzare lo i seguenti attributi per evitare questo tipo di conflitti:

Classe Autore impostazione listener Attributo
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Puoi usare questi due meccanismi, descritti in dettaglio nelle sezioni seguire per gestire un evento:

  • Riferimenti ai metodi: nelle espressioni, puoi metodi di riferimento conformi alla firma del metodo listener. Quando un'espressione restituisce un riferimento a un metodo, l'associazione di dati racchiude il metodo l'oggetto riferimento e proprietario in un listener e imposta tale listener nel vista target. Se l'espressione restituisce null, l'associazione di dati non crea un listener e imposta invece un listener null.
  • Associazioni listener: si tratta di espressioni lambda che vengono valutati quando si verifica l'evento. L'associazione di dati crea sempre , che imposta nella vista. Quando l'evento viene inviato, listener valuta l'espressione lambda.

Riferimenti ai metodi

Puoi associare direttamente gli eventi ai metodi di gestore, in modo simile assegna android:onClick a un in un'attività. Un vantaggio rispetto alla L'attributo onClick View è che viene elaborata al momento della compilazione. Quindi, se il metodo non esiste o la firma non è corretta, ricevi un errore durante la compilazione.

La principale differenza tra i riferimenti ai metodi e le associazioni listener è che il valore l'implementazione effettiva del listener viene creata quando vengono associati i dati, non quando . Se preferisci valutare l'espressione quando l'evento Usa le associazioni di ascoltatori.

Per assegnare un evento al relativo gestore, utilizza una normale espressione di associazione, con che rappresenta il nome del metodo da chiamare. Considera ad esempio il seguente esempio 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 di clic per una vista al onClickFriend(), come segue:

<?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. Loro sono simili ai riferimenti ai metodi, ma consentono di eseguire associazioni di dati arbitrarie le espressioni regolari. Questa funzionalità è disponibile con il plug-in Android Gradle per Gradle versione 2.0 e successive.

Nei riferimenti al metodo, i parametri del metodo devono corrispondere a quelli del il listener di eventi. Nelle associazioni di listener, solo il valore restituito deve corrispondere al valore valore restituito previsto del listener, a meno che non sia previsto void. Per ad esempio, considera la seguente classe presentatore che ha un 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 si utilizza un callback in un'espressione, l'associazione di dati crea automaticamente la listener necessario e lo registra per l'evento. Quando la vista attiva l'associazione di dati valuta l'espressione specificata. Come per l'associazione normale di associazione di dati, ottieni la sicurezza null e dei thread dell'associazione di dati mentre È in corso la valutazione delle espressioni listener.

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

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

Per utilizzare il parametro nell'espressione, procedi nel seguente modo:

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 di tipo diverso da void, il tuo devono restituire lo stesso tipo di valore. Ad esempio, se vuoi di ascoltare il tocco e di blocco (clic lungo), l'espressione deve restituire un 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 degli oggetti null, l'associazione di dati viene restituita il valore predefinito per il tipo in questione, 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 semplificare la lettura del codice. Il giorno Dall'altra parte, i listener che contengono espressioni complesse rendono i tuoi layout più rigidi da leggere e gestire. Mantieni le espressioni semplici come la trasmissione dei dati disponibili dall'interfaccia utente al metodo di callback. Implementa qualsiasi logica di business all'interno metodo di callback richiamato dall'espressione listener.

Importazioni, variabili e include

La libreria di associazioni di dati offre funzionalità quali importazioni, variabili e inclusi. Le importazioni consentono di creare 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 nella tua 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. La L'esempio di codice riportato di seguito 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 VISIBLE e costanti 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 classi, puoi rinominare una delle classi in un alias. L'esempio seguente rinomina la classe View nel Pacchetto com.example.real.estate per 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 all'interno del file di layout.

Importa altri corsi

Puoi utilizzare i tipi importati come riferimenti al tipo in variabili ed espressioni. La 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. Le seguenti ad 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 campi e metodi statici in le espressioni regolari. Il seguente codice importa la classe e i riferimenti MyStringUtils il 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. Ciascuna L'elemento variable descrive una proprietà che può essere impostata sul layout da utilizzare nelle espressioni di associazione all'interno del file di layout. L'esempio seguente dichiara 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 esaminati al momento della compilazione, pertanto se una variabile implementa Observable o è un raccolta osservabile, che deve riflettersi nel tipo. Se la variabile è una classe base o un'interfaccia che non implementa l'interfaccia Observable, le variabili non sono osservati.

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

La classe di associazione generata ha un setter e un getter per ciascuno dei come la codifica one-hot delle variabili categoriche. Le variabili assumono i valori predefiniti del codice gestito fino al setter è chiamato: 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 dell'oggetto o eliminare definitivamente una versione archiviata, in base alle necessità. Il valore di context è Context dal pannello della vista principale getContext(). La La variabile context è sostituita da una dichiarazione di variabile esplicita con questa nome.

Include

Puoi trasferire le variabili nell'associazione di un layout incluso dalla utilizzando lo spazio dei nomi dell'app e il nome della variabile in un attributo. La l'esempio seguente mostra le variabili user incluse da name.xml e contact.xml file di layout:

<?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 saperne di più sull'associazione di dati, consulta le seguenti risorse aggiuntive.

Campioni

Codelab

Blog post