Макеты и выражения привязки

Язык выражений позволяет писать выражения, обрабатывающие события, отправляемые представлениями. Библиотека привязки данных автоматически генерирует классы, необходимые для привязки представлений в макете к объектам данных.

Файлы макета привязки данных немного отличаются и начинаются с корневого тега layout, за которым следуют элемент data и корневой элемент view. Этот элемент представления является вашим корнем в файле макета без привязки. Ниже приведен пример файла макета.

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

Переменная user в data описывает свойство, которое можно использовать в этом макете:

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

Выражения в макете записываются в свойствах атрибутов с использованием синтаксиса @{}. В примере ниже текст TextView задается для свойства firstName переменной user:

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

Объекты данных

Предположим, что у вас есть простой объект, описывающий сущность 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;
  }
}

Объекты этого типа содержат данные, которые никогда не меняются. В приложениях часто встречаются данные, которые считываются один раз и больше не меняются. Также можно использовать объект, который соответствует набору соглашений, например использует методы доступа в языке программирования Java, как показано в следующем примере:

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

С точки зрения привязки данных эти два класса эквивалентны. Выражение @{user.firstName}, используемое для атрибута android:text, обращается к полю firstName в первом классе и методу getFirstName() во втором. Если метод firstName() существует, то он также будет разрешен.

Привязка данных

Для каждого файла макета создается класс привязки. По умолчанию название класса основано на названии файла макета, преобразованном в стиль PascalCase, с добавлением суффикса Binding. Например, если файл макета называется activity_main.xml, то сгенерированный класс привязки будет называться ActivityMainBinding.

Этот класс содержит все привязки из свойств макета (например, переменную user) к представлениям макета и знает, как присваивать значения выражениям привязки. Мы рекомендуем создавать привязки при раздувании макета, как показано в следующем примере:

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

При запуске приложение показывает в интерфейсе пользователя Test. Вы также можете получить представление с помощью LayoutInflater, как показано в следующем примере:

Kotlin

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

Java

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

Если вы используете элементы привязки данных в адаптере Fragment, ListView или RecyclerView, то можете использовать методы inflate() классов привязки или класс DataBindingUtil, как показано в примере кода ниже.

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

Язык выражений

Общие функции

Язык выражений очень похож на выражения, которые встречаются в управляемом коде. В языке выражений можно использовать следующие операторы и ключевые слова:

  • Математика: + - / * %
  • Объединение строк: +
  • Логический: && ||
  • Исполняемый файл: & | ^
  • Унарный: + - ! ~
  • Смена: >> >>> <<
  • Сравнение: == > < >= <= (символ < нужно экранировать как &lt;)
  • instanceof
  • Группировка: ()
  • Литералы, такие как символы, строки, числа, null.
  • Трансляция
  • Вызовы методов
  • Доступ к полям
  • Доступ к массиву: []
  • Тернарный оператор: ?:

Вот несколько примеров:

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

Отсутствующие операции

В синтаксисе выражений, который можно использовать в управляемом коде, отсутствуют следующие операции:

  • this
  • super
  • new
  • Явный общий вызов

Оператор объединения с null

Оператор объединения с null (??) выбирает левый операнд, если он не равен null, или правый, если левый равен null:

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

Это функционально эквивалентно следующему:

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

Ссылки на объекты

Выражение может ссылаться на свойство в классе, используя следующий формат, который одинаков для полей, геттеров и объектов ObservableField:

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

Как избежать исключений нулевого указателя

Сгенерированный код привязки данных автоматически проверяет значения null и предотвращает исключения нулевого указателя. Например, в выражении @{user.name}, если user имеет значение null, user.name присваивается значение по умолчанию null. Если вы ссылаетесь на user.age, где age – это тип int, то привязка данных использует значение по умолчанию 0.

Посмотреть ссылки

Выражение может ссылаться на другие представления в макете по идентификатору, используя следующий синтаксис:

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

В следующем примере представление TextView ссылается на представление EditText в том же макете:

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

Подборки

Для удобства вы можете использовать оператор [], чтобы получать доступ к распространенным коллекциям, таким как массивы, списки, разреженные списки и карты.

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

Вы также можете ссылаться на значение в карте, используя нотацию object.key. Например, в предыдущем примере можно заменить @{map[key]} на @{map.key}.

Строковые литералы

Значение атрибута можно заключить в одинарные кавычки, чтобы использовать двойные кавычки в выражении, как показано в следующем примере:

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

Значение атрибута также можно заключить в двойные кавычки. При этом строковые литералы должны быть заключены в обратные кавычки `, как показано ниже:

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

Ресурсы

Выражение может ссылаться на ресурсы приложения со следующим синтаксисом:

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

Вы можете оценить строки формата и множественные числа, указав параметры:

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

В качестве параметров ресурса можно передавать ссылки на ресурсы и ссылки на представления.

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

Если во множественном числе используется несколько параметров, передайте все параметры:


  Have an orange
  Have %d oranges

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

Для некоторых ресурсов требуется явное указание типа, как показано в таблице ниже.

Тип Обычный цифровой отпечаток Справочник по выражениям
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Управление событиями

Привязка данных позволяет писать выражения, обрабатывающие события, которые отправляются из представлений, например метод onClick(). Названия атрибутов событий определяются названиями методов прослушивателя (за некоторыми исключениями). Например, у события View.OnClickListener есть метод onClick(), поэтому атрибут для этого события – android:onClick.

Для события клика есть специальные обработчики событий, которым требуется атрибут, отличный от android:onClick, чтобы избежать конфликта. Чтобы избежать конфликтов, используйте следующие атрибуты:

Класс Настройка прослушивателя Атрибут
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Для обработки событий можно использовать два механизма, которые подробно описаны в следующих разделах:

  • Ссылки на методы. В выражениях можно ссылаться на методы, соответствующие сигнатуре метода прослушивателя. Когда выражение вычисляется как ссылка на метод, привязка данных упаковывает ссылку на метод и объект-владелец в прослушиватель и устанавливает этот прослушиватель в целевом представлении. Если выражение оценивается как null, привязка данных не создает прослушиватель, а вместо этого устанавливает прослушиватель null.
  • Привязки прослушивателя. Это лямбда-выражения, которые вычисляются при возникновении события. Привязка данных всегда создает слушатель, который устанавливается в представлении. Когда событие отправляется, прослушиватель оценивает лямбда-выражение.

Ссылки на методы

Вы можете привязывать события к методам обработчика напрямую, аналогично тому, как вы можете назначить android:onClick методу в действии. Одно из преимуществ по сравнению с атрибутом View onClick заключается в том, что выражение обрабатывается во время компиляции. Поэтому, если метод не существует или его сигнатура неверна, вы получите ошибку времени компиляции.

Основное различие между ссылками на методы и привязками прослушивателей заключается в том, что фактическая реализация прослушивателя создается при привязке данных, а не при запуске события. Если вы хотите, чтобы выражение вычислялось при возникновении события, используйте привязки прослушивателя.

Чтобы назначить событие обработчику, используйте обычное выражение привязки, в котором значением будет название вызываемого метода. Рассмотрим следующий пример объекта данных макета:

Kotlin

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

Java

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

Выражение привязки может назначить прослушиватель кликов для представления методу onClickFriend() следующим образом:

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

Привязки прослушивателя

Привязки прослушивателей – это выражения привязки, которые выполняются при возникновении события. Они похожи на ссылки на методы, но позволяют выполнять произвольные выражения привязки данных. Эта функция доступна в плагине Android Gradle для Gradle версии 2.0 и более поздних.

В ссылках на методы параметры метода должны соответствовать параметрам прослушивателя событий. В привязках прослушивателя только возвращаемое значение должно соответствовать ожидаемому возвращаемому значению прослушивателя, если только не ожидается void. Например, рассмотрим следующий класс presenter, у которого есть метод onSaveClick():

Kotlin

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

Java

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

Вы можете привязать событие клика к методу onSaveClick() следующим образом:

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

Когда обратный вызов используется в выражении, привязка данных автоматически создает необходимый прослушиватель и регистрирует его для события. Когда представление активирует событие, привязка данных оценивает заданное выражение. Как и в случае с обычными выражениями привязки, вы получаете нулевую и потоковую безопасность привязки данных, пока эти выражения прослушивателя оцениваются.

В примере выше параметр view, который передается в onClick(View), не определен. Привязки прослушивателя предоставляют два варианта параметров прослушивателя: вы можете игнорировать все параметры метода или назвать их все. Если вы предпочитаете указывать названия параметров, то можете использовать их в выражении. Например, предыдущее выражение можно записать следующим образом:

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

Если вы хотите использовать параметр в выражении, это можно сделать следующим образом:

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

Вы также можете использовать лямбда-выражение с несколькими параметрами:

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

Если событие, которое вы отслеживаете, возвращает значение, тип которого не является void, ваши выражения также должны возвращать значение того же типа. Например, если вы хотите отслеживать событие нажатия и удержания (длительного нажатия), выражение должно возвращать логическое значение.

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

Если выражение не может быть вычислено из-за объектов null, привязка данных возвращает значение по умолчанию для этого типа, например null для ссылочных типов, 0 для int или false для boolean.

Если вам нужно использовать выражение с предикатом, например тернарное, вы можете использовать void как символ:

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

Избегайте сложных прослушивателей

Выражения прослушивателя – это мощный инструмент, который может сделать ваш код более понятным. С другой стороны, слушатели, содержащие сложные выражения, делают ваши макеты более сложными для чтения и обслуживания. Выражения должны быть максимально простыми, например передавать доступные данные из интерфейса в метод обратного вызова. Реализуйте любую бизнес-логику в методе обратного вызова, который вы вызываете из выражения прослушивателя.

Импорт, переменные и включения

Библиотека привязки данных предоставляет такие функции, как импорт, переменные и включения. Импорт упрощает ссылки на классы в файлах макета. Переменные позволяют описать свойство, которое можно использовать в выражениях привязки. Includes позволяет повторно использовать сложные макеты в приложении.

Импорт

Импорт позволяет ссылаться на классы внутри файла макета, как в управляемом коде. Внутри элемента data можно использовать любое количество элементов import. В примере ниже показано, как импортировать класс View в файл макета:

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

Импорт класса View позволяет ссылаться на него из выражений привязки. В следующем примере показано, как ссылаться на константы VISIBLE и GONE класса View:

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

Псевдонимы типов

Если имена классов конфликтуют, вы можете переименовать один из классов в псевдоним. В примере ниже класс View в пакете com.example.real.estate переименовывается в Vista:

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

Затем вы можете использовать Vista для ссылки на com.example.real.estate.View и View для ссылки на android.view.View в файле макета.

Импортировать другие курсы

Импортированные типы можно использовать в качестве ссылок на типы в переменных и выражениях. В следующем примере User и 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>

Импортированные типы можно использовать для приведения части выражения. В следующем примере свойство connection преобразуется в тип User:

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

Импортированные типы также можно использовать при ссылках на статические поля и методы в выражениях. В следующем коде импортируется класс MyStringUtils и указывается ссылка на его метод 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"/>

Как и в управляемом коде, java.lang.* импортируется автоматически.

Переменные

Внутри элемента data можно использовать несколько элементов variable. Каждый элемент variable описывает свойство, которое можно задать в макете для использования в выражениях привязки в файле макета. В примере ниже объявляются переменные user, image и 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>

Типы переменных проверяются во время компиляции, поэтому если переменная реализует Observable или является наблюдаемой коллекцией, это должно быть отражено в типе. Если переменная является базовым классом или интерфейсом, который не реализует интерфейс Observable, переменные не отслеживаются.

Если для разных конфигураций (например, альбомной или портретной ориентации) используются разные файлы макетов, переменные объединяются. В этих файлах не должно быть конфликтующих определений переменных.

Сгенерированный класс привязки имеет сеттер и геттер для каждой из описанных переменных. Переменные принимают значения по умолчанию, пока не будет вызван метод setter: null для ссылочных типов, 0 для int, false для boolean и т. д.

При необходимости для использования в выражениях привязки создается специальная переменная context. Значение context – это объект Context из метода getContext() корневого представления. Переменная context переопределяется явным объявлением переменной с таким именем.

Что включено

Вы можете передавать переменные в привязку включенного макета из содержащего его макета, используя пространство имен приложения и имя переменной в атрибуте. В примере ниже показаны переменные user, включенные из файлов макета name.xml и 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>

Привязка данных не поддерживает включение в качестве прямого дочернего элемента элемента слияния. Например, не поддерживается следующий макет:

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

Дополнительные ресурсы

Дополнительную информацию о привязке данных можно найти в следующих ресурсах.

Образцы

Практические работы

Записи в блоге