Układy i wyrażenia powiązania

Język wyrażeń umożliwia pisanie wyrażeń obsługujących zdarzenia wysyłane przez widoki. Biblioteka powiązań danych automatycznie generuje klasy wymagane do powiązania widoków danych w układzie z obiektami danych.

Pliki układu wiązań danych są nieco inne i zaczynają się od tagu głównego layout, po którym znajdują się element data i element główny view. Ten element widoku znajduje się w katalogu głównym w niewiążącym pliku z układem. Oto przykładowy plik układu:

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

Zmienna user w data opisuje właściwość, której można używać w tym układzie:

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

Wyrażenia w układzie są zapisywane we właściwościach atrybutów przy użyciu składni @{}. W tym przykładzie tekst TextView jest ustawiony na właściwość firstName zmiennej user:

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

Obiekty danych

Załóżmy, że masz zwykły obiekt opisujący encję 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;
  }
}

Ten typ obiektu zawiera dane, które nigdy się nie zmieniają. W aplikacjach często zdarza się, że dane są odczytywane raz, a później się nie zmieniają. Możesz też używać obiektu zgodnego z określonymi konwencjami, jak np. stosować w języku programowania Java metody akcesorów, jak w tym przykładzie:

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

Z punktu widzenia wiązania danych te 2 klasy są równoważne. Wyrażenie @{user.firstName} użyte w atrybucie android:text uzyskuje dostęp do pola firstName w poprzedniej klasie i do metody getFirstName() w drugiej. Zostanie też rozpoznany jako firstName(), jeśli istnieje ta metoda.

Dane powiązania

Klasa powiązania jest generowana dla każdego pliku układu. Domyślnie nazwa klasy składa się z nazwy pliku układu przekonwertowanego na wielkość liter w formacie Pascal i z dodanym sufiksem Binding. Na przykład poprzednia nazwa pliku układu to activity_main.xml, więc odpowiadająca jej wygenerowana klasa powiązania to ActivityMainBinding.

Ta klasa zawiera wszystkie powiązania właściwości układu (np. zmienną user) z widokami układu i wie, jak przypisywać wartości do wyrażeń powiązań. Zalecamy tworzenie wiązań podczas nadmuchiwania układu, jak pokazano w tym przykładzie:

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

W czasie działania aplikacji wyświetla w interfejsie użytkownika Test. Widok możesz też wyświetlić za pomocą LayoutInflater, jak w tym przykładzie:

Kotlin

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

Java

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

Jeśli używasz elementów wiązań danych w adapterze Fragment, ListView lub RecyclerView, możesz skorzystać z metod klas wiązań inflate() lub klasy DataBindingUtil, jak pokazano w tym przykładzie kodu:

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

Język wyrażeń

Wspólne funkcje

Język wyrażeń przypomina wyrażenia w kodzie zarządzanym. W języku wyrażeń możesz używać tych operatorów i słów kluczowych:

  • Funkcja matematyczna: + - / * %
  • Łączenie ciągów znaków: +
  • Logiczne: && ||
  • Plik binarny: & | ^
  • Jednoargumentowy: + - ! ~
  • Przesunięcie: >> >>> <<
  • Porównanie: == > < >= <= (element < musi mieć postać &lt;)
  • instanceof
  • Grupowanie: ()
  • Literały, np. znak, ciąg znaków, cyfry, null
  • Prześlij
  • Wywołania metody
  • Dostęp do pól
  • Dostęp do tablicy: []
  • Operator potrójny: ?:

Oto przykłady:

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

Brakujące operacje

W składni wyrażenia, której można używać w kodzie zarządzanym, brakuje tych operacji:

  • this
  • super
  • new
  • Jawne wywołanie ogólne

Operator coalescingu o wartości null

Operator łączenia o wartości null (??) wybiera lewy operand, jeśli jest inny niż null, lub prawo, jeśli pierwszy to null:

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

Jest to odpowiednik tej funkcji:

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

Informacje o usługach

Wyrażenie może odwoływać się do właściwości w klasie przy użyciu poniższego formatu, który jest taki sam w przypadku pól, metod pobierania i obiektów ObservableField:

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

Unikaj wyjątków dla wskaźnika o wartości null

Wygenerowany kod powiązania danych automatycznie sprawdza wartości null i omija wyjątki wskaźnika zerowego. Na przykład w wyrażeniu @{user.name}, jeśli user ma wartość null, do parametru user.name przypisana jest wartość domyślna null. Jeśli odwołujesz się do user.age, gdzie wiek jest typu int, wiązanie danych użyje wartości domyślnej 0.

Zobacz odniesienia

Wyrażenie może odwoływać się do innych widoków w układzie według identyfikatora, korzystając z tej składni:

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

W poniższym przykładzie widok TextView odwołuje się do widoku EditText w tym samym układzie:

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

Kolekcje

Aby uzyskać dostęp do typowych kolekcji, takich jak tablice, listy, listy rozproszone i mapy, dla wygody możesz użyć operatora [].

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

Możesz też odwołać się do wartości na mapie, używając notacji object.key. Na przykład możesz zastąpić @{map[key]} w poprzednim przykładzie ciągiem @{map.key}.

literały ciągów znaków

Aby otaczać wartość atrybutu, możesz użyć cudzysłowów prostych, co pozwoli na użycie w wyrażeniu cudzysłowu, jak pokazano w następującym przykładzie:

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

Możesz też umieścić wartość atrybutu w cudzysłowie. W takim przypadku literały ciągów muszą być otoczone grawiskami `, jak pokazano tutaj:

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

Zasoby

Wyrażenie może odwoływać się do zasobów aplikacji, korzystając z tej składni:

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

Ciągi formatu i liczbę mnogą możesz obliczać, podając parametry:

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

Jako parametry zasobów możesz przekazywać odwołania do usług i pliki referencyjne:

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

Gdy liczba mnoga przyjmuje wiele parametrów, przekaż wszystkie parametry:


  Have an orange
  Have %d oranges

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

Niektóre zasoby wymagają wyraźnej oceny typu. Jak pokazano w tej tabeli:

Typ Normalne odniesienie Odwołanie do wyrażenia
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Obsługa zdarzeń

Powiązanie danych umożliwia zapisywanie zdarzeń obsługi wyrażeń wysyłanych z widoków, np. metody onClick(). Nazwy atrybutów zdarzeń są określane na podstawie nazwy metody detektora (z kilkoma wyjątkami). Na przykład View.OnClickListener ma metodę onClick(), więc atrybut tego zdarzenia to android:onClick.

Istnieją wyspecjalizowane moduły obsługi zdarzeń kliknięcia, które w celu uniknięcia konfliktu wymagają innego atrybutu niż android:onClick. Aby uniknąć takich konfliktów, możesz użyć tych atrybutów:

Kategoria Konfigurujący nasłuchiwanie Atrybut
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Do obsługi zdarzeń możesz wykorzystać 2 mechanizmy opisane szczegółowo w sekcjach poniżej:

  • Odwołania do metod: w wyrażeniach możesz się odwoływać do metod zgodnych z podpisem metody detektora. Gdy wyrażenie przyjmuje wyniki do odniesienia do metody, wiązanie danych pakuje odwołanie do metody i obiekt właściciela w odbiorniku oraz ustawia ten detektor w widoku docelowym. Jeśli wyrażenie zwraca wartość null, powiązanie danych nie utworzy odbiornika i ustawi odbiornik null.
  • Powiązania detektora: wyrażenia lambda, które są oceniane, gdy wystąpi zdarzenie. Powiązanie danych zawsze tworzy detektor, który ustawia w widoku. Po wysłaniu zdarzenia odbiornik ocenia wyrażenie lambda.

Odwołania do metod

Zdarzenia możesz powiązać bezpośrednio z metodami obsługi, podobnie jak w przypadku przypisywania android:onClick do metody w aktywności. Jedną z zalet atrybutu View onClick jest to, że wyrażenie jest przetwarzane w czasie kompilacji. Jeśli więc metoda nie istnieje lub jej podpis jest nieprawidłowy, pojawi się błąd czasu kompilacji.

Główna różnica między odwołaniami do metod a powiązaniami detektora polega na tym, że rzeczywista implementacja detektora jest tworzona, gdy dane są powiązane, a nie po wywołaniu zdarzenia. Jeśli wolisz oceniać wyrażenie w momencie występowania zdarzenia, użyj powiązań detektora.

Aby przypisać zdarzenie do jego modułu obsługi, użyj normalnego wyrażenia wiązania, gdzie wartość jest nazwą do wywołania. Przyjrzyjmy się np. temu obiektowi danych układu:

Kotlin

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

Java

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

Wyrażenie powiązania może przypisać odbiornik kliknięć widoku do metody onClickFriend() w ten sposób:

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

Powiązania detektora

Powiązania detektora to wyrażenia powiązania, które są uruchamiane, gdy występuje zdarzenie. Są podobne do odwołań do metod, ale umożliwiają uruchamianie dowolnych wyrażeń wiązań danych. Ta funkcja jest dostępna we wtyczce Androida do obsługi Gradle dla Gradle w wersji 2.0 i nowszych.

W odwołaniach do metod parametry metody muszą być zgodne z parametrami detektora. W powiązaniach detektora tylko zwracana wartość musi odpowiadać oczekiwanej wartości zwracanej przez detektor, chyba że oczekiwany jest void. Rozważmy na przykład tę klasę prezentującą, która ma metodę onSaveClick():

Kotlin

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

Java

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

Możesz powiązać zdarzenie kliknięcia z metodą onSaveClick() w ten sposób:

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

Jeśli w wyrażeniu używane jest wywołanie zwrotne, powiązanie danych automatycznie tworzy wymagany detektor i rejestruje go dla zdarzenia. Gdy widok uruchamia zdarzenie, powiązanie danych ocenia dane wyrażenie. Podobnie jak w przypadku wyrażeń regularnych, podczas oceny tych wyrażeń detektora otrzymujesz wartość null i bezpieczeństwo wiązania danych o wartości null.

W poprzednim przykładzie parametr view przekazywany do onClick(View) nie jest zdefiniowany. Powiązania detektora udostępniają 2 opcje parametrów detektora: możesz zignorować wszystkie parametry metody lub nazwać je wszystkie. Jeśli wolisz nazwać parametry, możesz ich użyć w wyrażeniu. Poprzednie wyrażenie możesz np. zapisać w ten sposób:

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

Jeśli chcesz użyć tego parametru w wyrażeniu, możesz to zrobić w ten sposób:

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

Wyrażenia lambda możesz też używać z więcej niż jednym parametrem:

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

Jeśli nasłuchiwane zdarzenie zwraca wartość inną niż void, wyrażenia muszą też zwracać ten sam typ wartości. Jeśli np. chcesz wykrywać zdarzenie „dotknij i przytrzymaj (długie kliknięcie”), wyrażenie musi zwracać wartość logiczną.

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

Jeśli nie można ocenić wyrażenia z powodu obiektów null, wiązanie danych zwraca wartość domyślną tego typu, np. null dla typów odwołań, 0 dla int lub false dla boolean.

Jeśli chcesz użyć wyrażenia z predykatem, np. trójargumentowego, możesz użyć void jako symbolu:

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

Unikaj skomplikowanych detektorów

Wyrażenia słuchaczy mają duże możliwości i mogą ułatwić czytelnikowi zrozumienie kodu. Z drugiej strony detektory zawierające wyrażenia złożone sprawiają, że układy są trudniejsze do odczytu i utrzymywania. Używaj wyrażeń tak prostych jak przekazywanie dostępnych danych z interfejsu użytkownika do metody wywołania zwrotnego. Zaimplementuj dowolną logikę biznesową w metodzie wywołania zwrotnego wywoływanej z wyrażenia detektora.

Importy, zmienne i uwzględnianie

Biblioteka powiązań danych udostępnia takie funkcje jak importowanie, zmienne i uwzględnianie. Importy umożliwiają łatwe odwoływanie się do klas w plikach układu. Zmienne umożliwiają opisanie właściwości, której można używać w wyrażeniach wiążących. Obejmuje możliwość ponownego używania złożonych układów w aplikacji.

Importy

Importy umożliwiają odwoływanie się do klas w pliku układu, tak jak w kodzie zarządzanym. W elemencie data można używać nie więcej niż 0 elementów import. Ten przykładowy kod importuje klasę View do pliku układu:

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

Zaimportowanie klasy View umożliwia odwoływanie się do niej z wyrażeń powiązania. Z przykładu poniżej dowiesz się, jak odwoływać się do stałych VISIBLE i GONE klasy View:

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

Aliasy typu

Jeśli występują konflikty nazw klas, możesz zmienić nazwę jednej z klas na alias. Ten przykład zmienia nazwę klasy View w pakiecie com.example.real.estate na Vista:

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

Następnie możesz użyć elementu Vista, aby odwoływać się do elementów com.example.real.estate.View i View, aby odwołać się do android.view.View w pliku układu.

Importuj inne zajęcia

Zaimportowanych typów możesz używać jako odwołań do typów w zmiennych i wyrażeniach. Poniższy przykład przedstawia typ zmiennej User i List:

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

Zaimportowanych typów możesz używać do rzutowania części wyrażenia. Ten przykład pozwala rzutować właściwość connection na typ User:

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

Zaimportowanych typów możesz też używać, gdy odwołują się do statycznych pól i metod w wyrażeniach. Ten kod importuje klasę MyStringUtils i odwołuje się do jej metody 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"/>

Podobnie jak w przypadku kodu zarządzanego, java.lang.* jest importowany automatycznie.

Zmienne

W elemencie data możesz używać wielu elementów variable. Każdy element variable opisuje właściwość, którą można ustawić w układzie, aby była ona używana w wyrażeniach powiązań w pliku układu. W tym przykładzie deklarowane są zmienne user, image i 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>

Typy zmiennych są sprawdzane podczas kompilacji, więc jeśli zmienna implementuje Observable lub jest obserwowalną kolekcją, musi to być odzwierciedlone w typie. Jeśli zmienna to klasa bazowa lub interfejs, który nie implementuje interfejsu Observable, zmienne nie są rejestrowane.

Jeśli istnieją różne pliki układu dla różnych konfiguracji (np. dla orientacji poziomej lub pionowej), zmienne są łączone. Definicje zmiennych w tych plikach układu nie mogą być sprzeczne.

Wygenerowana klasa powiązania ma obiekt ustawiający i getter dla każdej z opisanych zmiennych. Zmienne przyjmują domyślne wartości kodu zarządzanego, dopóki element ustawiający nie zostanie wywołany – null dla typów odwołań, 0 dla int, false dla boolean itd.

W razie potrzeby generowana jest specjalna zmienna o nazwie context do wykorzystania w wyrażeniach powiązań. Wartość pola context to obiekt Context z metody getContext() widoku głównego. Zmienna context zostanie zastąpiona jawną deklaracją zmiennej o tej nazwie.

Obejmuje:

Możesz przekazywać zmienne do powiązania uwzględnionego układu z układu, który zawiera, korzystając z przestrzeni nazw aplikacji i nazwy zmiennej w atrybucie. Ten przykład pokazuje uwzględnione zmienne user z plików układu name.xml i 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>

Powiązanie danych nie obsługuje operacji uwzględniania jako bezpośredniego elementu podrzędnego scalonego elementu. Na przykład ten układ nie jest obsługiwany:

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

Dodatkowe materiały

Więcej informacji o wiązaniach danych znajdziesz w tych dodatkowych materiałach.

Próbki

Ćwiczenia z programowania

Posty na blogu