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ć<
) 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<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]}"
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 odbiorniknull
. - 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:
</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: -> 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: -> 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: -> 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: 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: -> 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: -> 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<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.