Adaptery do bindowania

Adaptery wiążące odpowiadają za wywoływanie odpowiednich wywołań platformy ustaw wartości. Jednym z przykładów jest ustawienie wartości właściwości, np. wywoływanie funkcji setText(). Inny jest na przykład ustawienie detektora zdarzeń, np. wywoływanie funkcji setOnClickListener() .

Biblioteka wiązań danych umożliwia określenie metody wywoływanej w celu ustawienia wartości, podać własną logikę wiązania i określić typ zwróconego obiektu, za pomocą przejściówek.

Ustawianie wartości atrybutów

Po każdej zmianie wartości granicznej wygenerowana klasa powiązania musi wywołać metodę ustawiającą za pomocą wyrażenia wiążącego. Funkcja wiązania danych Biblioteka określa metodę automatycznie. Można też jawnie zadeklarować lub podaj własną logikę wyboru metody.

Automatyczny wybór metody

W przypadku atrybutu o nazwie example biblioteka automatycznie znajduje metodę setExample(arg), który jako argument akceptuje zgodne typy. Przestrzeń nazw atrybutu nie jest brany pod uwagę. Używana jest tylko nazwa i typ atrybutu przy wyszukiwaniu metody.

Na przykład biblioteka w ramach wyrażenia android:text="@{user.name}" szuka metody setText(arg), która akceptuje typ zwracany przez user.getName() Jeśli zwracany typ user.getName() to String, to biblioteka szuka metody setText(), która akceptuje argument String. Jeśli zwraca wartość int, biblioteka wyszukuje metodę setText(), która akceptuje argument int. Wyrażenie musi zwracać poprawny typ. Dostępne opcje w razie potrzeby rzutować wartość zwrotną.

Powiązanie danych działa nawet wtedy, gdy nie ma atrybutu o podanej nazwie. Dostępne opcje tworzyć atrybuty dla dowolnej metody ustawiającej za pomocą powiązania danych. Na przykład zespół pomocy zajęcia DrawerLayout nie ma atrybutów, ale ma mnóstwo ustalających. Następujący układ automatycznie używa setScrimColor(int) oraz addDrawerListener(DrawerListener) jako metody ustawiania dla metod app:scrimColor i app:drawerListener odpowiednio:

<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ą elementy ustawiające, które nie pasują do nazwy. W takich sytuacjach można powiązać z metodą ustawiającą za pomocą atrybutu BindingMethods adnotacja. Adnotacja jest używana z klasą i może zawierać wiele BindingMethod adnotacji, po 1 dla każdej ze zmienionych metod. Metody powiązań to adnotacje, które które możesz dodawać do dowolnych zajęć w swojej aplikacji.

W poniższym przykładzie atrybut android:tint jest powiązany z parametrem setImageTintList(ColorStateList). – nie przy użyciu metody 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 ustawień w klasach platformy Androida. są już zaimplementowane z użyciem konwencji nazw, aby automatycznie i metody dopasowywania.

Dodaj niestandardowe funkcje logiczne

Niektóre atrybuty wymagają niestandardowej logiki wiązania. Na przykład nie ma powiązanych ustawiającą atrybut android:paddingLeft. Zamiast tego udostępniana jest metoda setPadding(left, top, right, bottom). Metoda adaptera wiązania statycznego z BindingAdapter umożliwia dostosowanie sposobu wywoływania funkcji ustawiającej dla atrybutu.

Atrybuty klas platformy Android mają już obiekt BindingAdapter adnotacji. Poniższy przykład pokazuje adapter wiązania dla Atrybut 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 do widoku danych powiązanego z atrybutem. Drugi parametr określa typ akceptowany w wyrażeniu wiązania dla danego atrybutu.

Adaptery wiązań są też przydatne do innych rodzajów dostosowywania. Przykład: w celu wczytania obrazu można wywołać niestandardowy program wczytujący z wątku instancji roboczej.

Możesz też używać przejściówek, które otrzymują wiele atrybutów, jak pokazano w następujący przykład:

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ć adaptera w swoim układzie, tak jak w przykładzie poniżej. Notatka które @drawable/venueError odnosi się do zasobu w Twojej aplikacji. Otoczenie zasób z @{} sprawia, że jest to prawidłowe wyrażenie powiązania.

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

Przejściówka jest wywoływana, jeśli interfejsy imageUrl i error są używane przez Obiekt ImageView, imageUrl to ciąg znaków, a error to Drawable Jeśli chcesz adapter, który ma być wywoływany po ustawieniu dowolnego z atrybutów, ustaw opcjonalny requireAll flagę adaptera na false, jak w poniższym 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 powiązań mogą przyjmować stare wartości w modułach obsługi. Metoda Przyjęcie starych i nowych wartości musi najpierw zadeklarować wszystkie stare wartości dla atrybutów, z nowymi wartościami, tak 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ć tylko z interfejsami lub klasami abstrakcyjnymi z jednym metody abstrakcyjnej, 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:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

Jeśli odbiornik ma wiele metod, musisz podzielić go na kilku odbiorników. Przykład: View.OnAttachStateChangeListener udostępnia 2 metody: onViewAttachedToWindow(View) oraz onViewDetachedFromWindow(View). Biblioteka ma dwa interfejsy do rozróżniania atrybutów i modułów obsługi dla nich:

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

Zmiana jednego detektora może oddziaływać na resztę, dlatego potrzebujesz przejściówki, działa w przypadku jednego z tych atrybutów lub obu tych atrybutów. Możesz ustawić: requireAll na false w: adnotacja wskazująca, że nie do każdego atrybutu trzeba przypisać powiązanie zgodnie z poniższym przykładem:

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 parametru addOnAttachStateChangeListener() oraz removeOnAttachStateChangeListener() zamiast metody wyznaczającej OnAttachStateChangeListener. Klasa android.databinding.adapters.ListenerUtil pomaga je śledzić z detektorami, aby można je było usunąć w adapterze wiązania.

Konwersje obiektów

Automatyczna konwersja obiektów

Kiedy Object jest zwracane z powiązania , biblioteka wybiera metodę używaną do ustawienia wartości argumentu usłudze. Pole Object jest rzutowane na typ parametru wybranej metody. Ten jest wygodne w aplikacjach, które używają ObservableMap zajęcia na jak 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 rzutować na typ parametru znaleziony w metodzie setText(CharSequence) używanej do ustaw wartość atrybutu android:text. Jeśli typ parametru to niejednoznaczne, rzutować zwracany typ w wyrażeniu.

Konwersje niestandardowe

W niektórych sytuacjach wymagana jest konwersja niestandardowa między określonymi typami. Dla: np. atrybut android:background widoku oczekuje Drawable, ale określona wartość color jest liczbą całkowitą. Poniższy przykład pokazuje który oczekuje wartości Drawable, ale jest podana liczba całkowita:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Gdy wartość Drawable jest oczekiwana i zwracana jest liczba całkowita, przekonwertuj int do ColorDrawable. Aby dokonać konwersji, użyj metody statycznej ze BindingConversion w następujący sposób:

Kotlin

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

Java

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

Typy wartości podane w wyrażeniu powiązania muszą być jednak spójne. W jednym wyrażeniu nie można używać różnych typów, jak pokazano na ilustracji poniżej przykład:

// 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 powiązaniach danych znajdziesz w tych materiałach.

Próbki

Ćwiczenia z programowania

Posty na blogu

. .