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

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

Файлы макета привязки данных немного отличаются и начинаются с корневого тега 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 :

Котлин

data class User(val firstName: String, val lastName: String)

Ява

public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

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

Котлин

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

Ява

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() , если этот метод существует.

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

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

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

Котлин

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

Ява

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

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

Котлин

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

Ява

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

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

Котлин

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

Ява

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 :

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 является то, что выражение обрабатывается во время компиляции. Итак, если метод не существует или его подпись неверна, вы получите ошибку времени компиляции.

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

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

Котлин

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

Ява

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 . Например, рассмотрим следующий класс презентатора, который имеет метод onSaveClick() :

Котлин

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

Ява

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

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

Котлин

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Ява

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

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

Котлин

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

Ява

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 , ваши выражения также должны возвращать значение того же типа. Например, если вы хотите прослушивать событие касания и удержания (длительного щелчка), ваше выражение должно возвращать логическое значение.

Котлин

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Ява

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

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

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

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

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

Импорт

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

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

Сгенерированный класс привязки имеет методы установки и получения для каждой из описанных переменных. Переменные принимают значения управляемого кода по умолчанию до тех пор, пока не будет вызван установщик — 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>

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

Чтобы узнать больше о привязке данных, обратитесь к следующим дополнительным ресурсам.

Образцы

Кодлабы

Сообщения в блоге