Адаптеры привязки отвечают за выполнение соответствующих вызовов платформы для установки значений. Одним из примеров является установка значения свойства, например вызов метода setText()
. Другой пример — установка прослушивателя событий, например вызов метода setOnClickListener()
.
Библиотека привязки данных позволяет указать метод, вызываемый для установки значения, предоставить собственную логику привязки и указать тип возвращаемого объекта с помощью адаптеров.
Установить значения атрибутов
При каждом изменении связанного значения созданный класс привязки должен вызывать метод установки представления с выражением привязки. Вы можете позволить библиотеке привязки данных автоматически определять метод или явно объявить метод или предоставить собственную логику для выбора метода.
Автоматический выбор метода
Для атрибута с именем example
библиотека автоматически находит метод setExample(arg)
, который принимает в качестве аргумента совместимые типы. Пространство имен атрибута не учитывается. При поиске метода используются только имя и тип атрибута.
Например, учитывая выражение android:text="@{user.name}"
библиотека ищет метод setText(arg)
, который принимает тип, возвращаемый user.getName()
. Если тип возвращаемого значения user.getName()
— String
, библиотека ищет метод setText()
, который принимает аргумент String
. Если выражение возвращает int
, библиотека ищет метод setText()
, который принимает аргумент int
. Выражение должно возвращать правильный тип. При необходимости вы можете привести возвращаемое значение.
Привязка данных работает, даже если атрибута с данным именем не существует. Вы можете создать атрибуты для любого установщика, используя привязку данных. Например, класс поддержки DrawerLayout
не имеет атрибутов, но имеет множество установщиков. Следующий макет автоматически использует методы setScrimColor(int)
и addDrawerListener(DrawerListener)
в качестве установщика атрибутов app:scrimColor
и app:drawerListener
соответственно:
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
Укажите имя пользовательского метода
У некоторых атрибутов есть установщики, имена которых не совпадают. В таких ситуациях атрибут можно связать с установщиком с помощью аннотации BindingMethods
. Аннотация используется с классом и может содержать несколько аннотаций BindingMethod
, по одной для каждого переименованного метода. Методы привязки — это аннотации, которые вы можете добавить в любой класс вашего приложения.
В следующем примере атрибут android:tint
связан с методом setImageTintList(ColorStateList)
, а не с методом setTint()
:
Котлин
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Ява
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
Обычно вам не нужно переименовывать установщики в классах платформы Android. Атрибуты уже реализованы с использованием соглашения об именах для автоматического поиска соответствующих методов.
Обеспечьте собственную логику
Для некоторых атрибутов требуется специальная логика привязки. Например, для атрибута android:paddingLeft
не существует связанного установщика. Вместо этого предоставляется метод setPadding(left, top, right, bottom)
. Метод адаптера статической привязки с аннотацией BindingAdapter
позволяет настроить способ вызова установщика атрибута.
Атрибуты классов платформы Android уже имеют аннотации BindingAdapter
. В следующем примере показан адаптер привязки для атрибута paddingLeft
:
Котлин
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Ява
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
Типы параметров важны. Первый параметр определяет тип представления, связанного с атрибутом. Второй параметр определяет тип, принятый в выражении привязки для данного атрибута.
Адаптеры привязки также полезны для других типов настройки. Например, из рабочего потока можно вызвать пользовательский загрузчик для загрузки изображения.
У вас также могут быть адаптеры, которые получают несколько атрибутов, как показано в следующем примере:
Котлин
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Ява
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
Вы можете использовать адаптер в своем макете, как показано в следующем примере. Обратите внимание, что @drawable/venueError
относится к ресурсу в вашем приложении. Окружение ресурса @{}
делает его допустимым выражением привязки.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
Адаптер вызывается, если imageUrl
и error
используются для объекта ImageView
, imageUrl
— это строка, а error
— это Drawable
. Если вы хотите, чтобы адаптер вызывался при установке любого из атрибутов, установите для дополнительного флага requireAll
адаптера значение false
, как показано в следующем примере:
Котлин
@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); } }
Ява
@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); } }
Методы адаптера привязки могут принимать в своих обработчиках старые значения. Метод, принимающий старые и новые значения, должен сначала объявить все старые значения атрибутов, а затем новые значения, как показано в следующем примере:
Котлин
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Ява
@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()); } }
Обработчики событий можно использовать только с интерфейсами или абстрактными классами с одним абстрактным методом, как показано в следующем примере:
Котлин
@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) } } }
Ява
@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); } } }
Используйте этот обработчик событий в своем макете следующим образом:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
Если у прослушивателя есть несколько методов, его необходимо разделить на несколько прослушивателей. Например, View.OnAttachStateChangeListener
имеет два метода: onViewAttachedToWindow(View)
и onViewDetachedFromWindow(View)
. Библиотека предоставляет два интерфейса для различения атрибутов и их обработчиков:
Котлин
// 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) }
Ява
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
Поскольку изменение одного прослушивателя может повлиять на другой, вам нужен адаптер, который работает с любым атрибутом или с обоими. Вы можете установить для requireAll
значение false
в аннотации, чтобы указать, что не каждому атрибуту должно быть назначено выражение привязки, как показано в следующем примере:
Котлин
@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) } } }
Ява
@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); } } }
Приведенный выше пример немного сложен, поскольку класс View
использует методы addOnAttachStateChangeListener()
и removeOnAttachStateChangeListener()
вместо метода установки для OnAttachStateChangeListener
. Класс android.databinding.adapters.ListenerUtil
помогает отслеживать эти прослушиватели, чтобы их можно было удалить в адаптере привязки.
Преобразования объектов
Автоматическое преобразование объектов
Когда Object
возвращается из выражения привязки, библиотека выбирает метод, используемый для установки значения свойства. Object
преобразуется к типу параметра выбранного метода. Такое поведение удобно в приложениях, использующих класс ObservableMap
для хранения данных, как показано в следующем примере:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Объект userMap
в выражении возвращает значение, которое автоматически преобразуется к типу параметра, найденному в методе setText(CharSequence)
используемом для установки значения атрибута android:text
. Если тип параметра неоднозначен, приведите тип возвращаемого значения в выражении.
Пользовательские преобразования
В некоторых ситуациях требуется пользовательское преобразование между конкретными типами. Например, атрибут представления android:background
ожидает Drawable
, но указанное значение color
является целым числом. В следующем примере показан атрибут, который ожидает Drawable
, но вместо него предоставляется целое число:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Всякий раз, когда ожидается Drawable
и возвращается целое число, преобразуйте int
в ColorDrawable
. Чтобы выполнить преобразование, используйте статический метод с аннотацией BindingConversion
, как показано ниже:
Котлин
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Ява
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
Однако типы значений, указанные в выражении привязки, должны быть согласованными. Вы не можете использовать разные типы в одном выражении, как показано в следующем примере:
// 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"/>
Дополнительные сведения о привязке данных см. в следующих ресурсах. Контент и образцы кода на этой странице предоставлены по лицензиям. Java и OpenJDK – это зарегистрированные товарные знаки корпорации Oracle и ее аффилированных лиц. Последнее обновление: 2024-08-28 UTC. Дополнительные ресурсы
Образцы
Кодлабы
Сообщения в блоге
{% дословно %} Рекомендуется для вас
{% дословно %}