В этом уроке рассматриваем возможность написания кода в 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
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня