Adaptery powiązań odpowiadają za wykonywanie odpowiednich wywołań platformy w celu ustawiania wartości. Możesz na przykład ustawić wartość właściwości, np. wywołać metodę setText()
. Innym przykładem jest ustawianie detektora zdarzeń, np. wywołanie metody setOnClickListener()
.
Biblioteka powiązań danych umożliwia określenie metody wywoływanej w celu ustawienia wartości, podanie własnej logiki wiązania i określenie typu zwracanego obiektu za pomocą adapterów.
Ustawianie wartości atrybutów
Po każdej zmianie wartości granicznej wygenerowana klasa wiązania musi wywołać w widoku metodę ustawiającą z wyrażeniem wiążącym. Możesz pozwolić, aby biblioteka wiązań danych automatycznie określiła metodę, albo możesz ją zadeklarować jawnie lub podać własną logikę wyboru metody.
Automatyczny wybór metody
W przypadku atrybutu o nazwie example
biblioteka automatycznie znajduje metodę setExample(arg)
, która jako argument akceptuje zgodne typy. Przestrzeń nazw atrybutu nie jest brana pod uwagę. Podczas wyszukiwania metody używane są tylko nazwa i typ atrybutu.
Na przykład w wyrażeniu android:text="@{user.name}"
biblioteka szuka metody setText(arg)
, która akceptuje typ zwracany przez user.getName()
. Jeśli zwracany typ elementu user.getName()
to String
, biblioteka szuka metody setText()
, która akceptuje argument String
. Jeśli wyrażenie zwraca wartość int
, biblioteka wyszukuje metodę setText()
, która akceptuje argument int
. Wyrażenie musi zwracać prawidłowy typ. W razie potrzeby możesz rzutować wartość zwracaną.
Powiązanie danych działa nawet wtedy, gdy żaden atrybut o podanej nazwie nie istnieje. Za pomocą powiązania danych możesz tworzyć atrybuty dla dowolnego systemu ustawiającego. Na przykład klasa pomocy DrawerLayout
nie ma atrybutów, ale ma dużo ustawiania. W tym układzie automatycznie używane są metody setScrimColor(int)
i addDrawerListener(DrawerListener)
jako metody ustawiające odpowiednio dla atrybutów app:scrimColor
i app:drawerListener
:
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
Podaj nazwę metody niestandardowej
Niektóre atrybuty mają ustawienia, które nie są zgodne pod względem nazwy. W takich sytuacjach atrybut można powiązać z metodą ustawiającą za pomocą adnotacji BindingMethods
. Adnotacja jest używana w klasie i może zawierać wiele adnotacji BindingMethod
, po jednej dla każdej metody ze zmienioną nazwą. Metody powiązania to adnotacje, które możesz
dodawać do dowolnej klasy w aplikacji.
W tym przykładzie atrybut android:tint
jest powiązany z metodą setImageTintList(ColorStateList)
, a nie z metodą setTint()
:
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
Zwykle nie musisz zmieniać nazw narzędzi ustawiających w klasach platformy Androida. Atrybuty są już zaimplementowane z wykorzystaniem konwencji nazw, która pozwala automatycznie znajdować metody dopasowania.
Wprowadź niestandardową logikę
Niektóre atrybuty wymagają niestandardowej logiki powiązania. Na przykład nie ma powiązanego parametru ustawiającego dla atrybutu android:paddingLeft
. Zamiast tego używana jest metoda setPadding(left,
top, right, bottom)
. Metoda statycznego wiązania wiązania z adnotacją BindingAdapter
pozwala dostosować sposób wywoływania metody ustawiającej dla atrybutu.
Atrybuty klas platformy Androida mają już adnotacje BindingAdapter
. Poniższy przykład pokazuje adapter wiązania dla atrybutu paddingLeft
:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
Typy parametrów są ważne. Pierwszy parametr określa typ widoku danych, który jest powiązany z atrybutem. Drugi parametr określa typ akceptowany w wyrażeniu powiązania dla danego atrybutu.
Adaptery do bindowania przydają się również do innych rodzajów dostosowywania. Na przykład niestandardowy program wczytywania może zostać wywołany z wątku instancji roboczej w celu wczytania obrazu.
Możesz też używać adapterów, które otrzymują wiele atrybutów, jak w tym przykładzie:
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
Możesz użyć przejściówki w układzie, jak pokazano w poniższym przykładzie. Pamiętaj, że @drawable/venueError
odnosi się do zasobu w Twojej aplikacji. Otoczenie zasobu za pomocą @{}
sprawia, że jest on prawidłowym wyrażeniem wiążącym.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
Adapter jest wywoływany, jeśli obiekty imageUrl
i error
są używane w obiekcie ImageView
, imageUrl
to ciąg znaków, a error
to Drawable
. Jeśli chcesz, by adapter był wywoływany po ustawieniu któregokolwiek z atrybutów, ustaw opcjonalną flagę requireAll
adaptera na false
, jak w tym przykładzie:
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Metody adaptera powiązania mogą przyjmować stare wartości z modułów obsługi. Metoda, która pobiera stare i nowe wartości, musi najpierw zadeklarować wszystkie stare wartości dla atrybutów, a po nich nowe wartości, jak w tym przykładzie:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } }
Modułów obsługi zdarzeń można używać z interfejsami lub klasami abstrakcyjnymi tylko z jedną metodą abstrakcyjną, jak w tym przykładzie:
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@BindingAdapter("android:onLayoutChange") public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } } }
Użyj tego modułu obsługi zdarzeń w układzie w ten sposób:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
Jeśli detektor ma wiele metod, należy go podzielić na większą liczbę detektorów.
Na przykład w polu View.OnAttachStateChangeListener
dostępne są 2 metody: onViewAttachedToWindow(View)
i onViewDetachedFromWindow(View)
.
Biblioteka udostępnia 2 interfejsy, które pozwalają rozróżniać atrybuty i moduły obsługi:
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
Ponieważ zmiana jednego nasłuchującego może wpływać na drugi, potrzebujesz adaptera, który będzie działać w przypadku jednego z tych atrybutów lub obu. W adnotacji możesz ustawić requireAll
na false
, aby określić, że nie do każdego atrybutu musi być przypisane wyrażenie powiązania, jak pokazano w poniższym przykładzie:
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
Powyższy przykład jest nieco skomplikowany, ponieważ klasa View
używa metod addOnAttachStateChangeListener()
i removeOnAttachStateChangeListener()
zamiast metody ustawiającej dla OnAttachStateChangeListener
.
Klasa android.databinding.adapters.ListenerUtil
pomaga śledzić te detektory i można je usunąć w adapterze wiązania.
Konwersje obiektów
Automatyczna konwersja obiektów
Gdy z wyrażenia powiązania zwracane jest Object
, biblioteka wybiera metodę używaną do ustawiania wartości właściwości. Element Object
jest rzutowany na typ parametru wybranej metody. Jest to wygodne w przypadku aplikacji używających klasy ObservableMap
do przechowywania danych. W tym przykładzie:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Obiekt userMap
w wyrażeniu zwraca wartość, która jest automatycznie rzutowana na typ parametru znaleziony w metodzie setText(CharSequence)
używanej do ustawiania wartości atrybutu android:text
. Jeśli typ parametru jest niejednoznaczny, rzutuj zwracany typ w wyrażeniu.
Konwersje niestandardowe
W niektórych sytuacjach między określonymi typami wymagana jest konwersja niestandardowa. Na przykład atrybut android:background
widoku oczekuje wartości Drawable
, a podana wartość color
jest liczbą całkowitą. Ten przykład pokazuje atrybut, który oczekuje wartości Drawable
, ale zamiast niej podana jest liczba całkowita:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Gdy oczekiwana jest wartość Drawable
i zwracana jest liczba całkowita, przekonwertuj int
na ColorDrawable
.
Aby dokonać konwersji, użyj metody statycznej z adnotacją BindingConversion
w ten sposób:
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
Jednak typy wartości podane w wyrażeniu powiązania muszą być spójne. W tym samym wyrażeniu nie można używać różnych typów, jak widać w tym przykładzie:
// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Dodatkowe materiały
Więcej informacji o wiązaniach danych znajdziesz w tych materiałach:
Próbki
Ćwiczenia z programowania
Posty na blogu
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Biblioteka powiązań danych
- Układy i wyrażenia powiązania
- Wyświetl powiązanie