В этом уроке рассматриваем возможность написания кода в layout и получаем View от биндинга.
Полный список уроков курса:
- Урок 1. Lifecycle
- Урок 2. LiveData
- Урок 3. LiveData. Дополнительные возможности
- Урок 4. ViewModel
- Урок 5. Room. Основы
- Урок 6. Room. Entity
- Урок 7. Room. Insert, Update, Delete, Transaction
- Урок 8. Room. Query
- Урок 9. Room. RxJava
- Урок 10. Room. Запрос из нескольких таблиц. Relation
- Урок 11. Room. Type converter
- Урок 12. Room. Миграция версий базы данных
- Урок 13. Room. Тестирование
- Урок 14. Paging Library. Основы
- Урок 15. Paging Library. PagedList и DataSource. Placeholders.
- Урок 16. Paging Library. LivePagedListBuilder. BoundaryCallback.
- Урок 17. Paging Library. Виды DataSource
- Урок 18. Android Data Binding. Основы
- Урок 19. Android Data Binding. Код в layout. Доступ к View
- Урок 20. Android Data Binding. Обработка событий
- Урок 21. Android Data Binding. Observable поля. Двусторонний биндинг.
- Урок 22. Android Data Binding. Adapter. Conversion.
- Урок 23. Android Data Binding. Использование с include, ViewStub и RecyclerView.
- Урок 24. Navigation Architecture Component. Введение
- Урок 25. Navigation. Передача данных. Type-safe аргументы.
- Урок 26. Navigation. Параметры навигации
- Урок 27. Navigation. NavigationUI.
- Урок 28. Navigation. Вложенный граф. Global Action. Deep Link.
- Урок 29. WorkManager. Введение
- Урок 30. WorkManager. Критерии запуска задачи.
- Урок 31. WorkManager. Последовательность выполнения задач.
- Урок 32. WorkManager. Передача и получение данных
- Урок 33. Практика. О чем это будет.
- Урок 34. Практика. TodoApp. Список задач.
- Урок 35. Практика. TodoApp. Просмотр задачи
Продолжаем говорить про DataBinding. Мы уже рассмотрели, как можно помещать значения из объектов в TextView. Но биндинг этим не ограничивается и дает нам возможность писать код прямо в layout.
Давайте рассмотрим примеры, когда это может понадобиться.
Есть класс Employee:
public class Employee {
public Employee(long id, String name, String address, int salary) {
this.id = id;
this.name = name;
this.address = address;
this.salary = salary;
}
public long id;
public String name;
public String address;
public int salary;
}
Мы хотим выводить на экран имя, адрес и зарплату.
Экран будет таким:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="employee"
type="ru.startandroid.application.data.Employee" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.address}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.salary}" />
</LinearLayout>
</layout>
А вызов биндинга таким:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Employee employee = new Employee(1, "John Smith", "London", 10000);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
binding.setEmployee(employee);
}
Ничего необычного. Все так же, как и в прошлом уроке.
Но при запуске получим ошибку: android.content.res.Resources$NotFoundException: String resource ID #0x2710
Так произошло, потому что биндинг попытался отобразить поле salary в TextView. Он просто выполнил код setText(employee.salary). И т.к. salary у нас имеет тип int, то TextView решил, что ему передают идентификатор строкового ресурса. И, конечно, он не нашел такую строку в strings.xml.
Это довольно часто возникающая ошибка. И в коде мы обычно решаем ее с помощью String.valueOf():
textView.setText(String.valueOf(employee.salary));
Биндинг позволяет сделать нам то же самое прямо в layout:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(employee.salary)}" />
Т.е. внутри @{ ... } мы можем писать простейший код и он будет выполнен.
Запустив приложение, мы увидим зарплату в TextView.
Рассмотрим еще несколько примеров:
Отображение в одном TextView сразу двух полей Employee
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{employee.name + ", " + employee.address}' />
Обратите внимание на кавычки. Т.к. нам нужны двойные кавычки, чтобы добавить запятую между name и address, то весь этот код мы помещаем в одинарные кавычки.
Видимость View в зависимости от значения поля
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.address}"
android:visibility="@{TextUtils.isEmpty(employee.address) ? View.GONE : View.VISIBLE}"/>
Адрес будет отображен, только если он не пустой. А при пустом адресе видимость этого TextView будет GONE.
Обратите внимание, что мы здесь используем классы TextUtils и View. Если сейчас попытаться запустить приложение, то мы получим следующую ошибку: Identifiers must have user defined types from the XML file. TextUtils is missing it
Биндинг говорит, что не знает ничего про TextUtils. Нам надо добавить его в import. Делается это в секции data.
<data> <import type="android.view.View"/> <import type="android.text.TextUtils"/> <variable .../> </data>
Теперь биндинг знает, какие классы мы имеем ввиду
Т.е. это аналогично тому, как в java коде вы пишете:
import android.text.TextUtils; import android.view.View;
и после этого можете использовать эти классы.
Использование resources значения: strings, dimens и пр.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{TextUtils.isEmpty(employee.address) ? @string/empty_address : employee.address}"/>
Если адрес пустой, то показываем заглушку из strings.
В примерах выше мы использовали в layout только одну переменную - Employee. Давайте добавим еще одну.
Создадим новый класс, который будет содержать информацию об отделе
public class Department {
public Department(long id, String name) {
this.id = id;
this.name = name;
}
public long id;
public String name;
}
Добавим переменную типа Department в layout
<data>
<variable
name="employee"
type="ru.startandroid.application.data.Employee" />
<variable
name="department"
type="ru.startandroid.application.data.Department" />
</data>
И используем ее:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{employee.name + "(" + department.name + ")"}' />
В одном TextView показываем данные из двух переменных.
Код выполнения биндинга будет выглядеть так:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Employee employee = new Employee(1, "John Smith", "", 10000);
Department department = new Department(100, "IT");
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
binding.setEmployee(employee);
binding.setDepartment(department);
}
Для переменной Department в классе MainActivityBinding был сгенерирован отдельный метод setDepartment.
Можно немного усложнить логику и показывать название отдела, только если мы передали объект Department в биндинг:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{employee.name + (department == null ? "" : " (" + department.name + ")") }' />
Показываем отдел, если department не null
Биндинг умеет работать и с коллекциями. Например, если в Employee есть поле со списком хобби:
public List<String> hobbies;
то, в layout мы можем отобразить первое хобби из списка следующим образом:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.hobbies[0]}" />
Если нам необходимо использовать список, как отдельную переменную в layout, то variable будет выглядеть так:
<variable name="hobbies" type="java.util.List<String>" />
Ограничения XML не позволяют просто так использовать символы < и >. Поэтому их приходится заменять спецсимволами < и >.
То же самое описание переменной, но List вынесен в импорт:
<import type="java.util.List"/> <variable name="hobbies" type="List<String>" />
Map коллекции описываются аналогично:
<import type="java.util.Map"/> <variable name="map" type="Map<String, String>"/>
Получение значения по ключу:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`key`]}" />
В официальной документации вы можете посмотреть полный список возможностей написания кода в layout.
View
Если нам нужны какие-либо View из нашего layout, то их можно получить из биндинга. Для этого необходимо, чтобы View имело id.
Например, если в layout есть поле:
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
то мы можем получить его из биндинга так:
TextView textViewName = binding.name;
Также можно получить корневое View методом getRoot:
View rootView = binding.getRoot();
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

