В этом уроке рассмотрим примеры использование Android Data Binding с include, ViewStub и RecyclerView
Полный список уроков курса:
- Урок 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. Просмотр задачи
include
Предполагается, что вы знакомы с тегом include, я не буду подробно о нем рассказывать.
С помощью include можно использовать layout файл внутри другого layout файла. При этом мы можем использовать биндинг без каких-либо дополнительных усилий.
Рассмотрим простой пример.
Основной layout файл main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<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">
<include layout="@layout/employee_details"
app:employee="@{employee}"/>
</LinearLayout>
</layout>
В include мы указываем, что здесь надо использовать layout из файла employee_details, и передаем переменную, которая этому вложенному layout понадобится.
Файл employee_details.xml выглядит, как обычный layout файл с биндингом.
<?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>
<merge>
<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="@{String.valueOf(employee.salary)}" />
</merge>
</layout>
Он получит переменную employee из внешнего layout и сможет использовать ее значения.
ViewStub
Предполагается, что вы знакомы с механизмом ViewStub, я не буду о нем подробно рассказывать.
ViewStub - это механизм для ленивого создания View. Т.е. если у вас в layout есть View, которые отображаются в исключительных случаях, то нет смысла создавать их при каждом отображении экрана. В этом случае лучше использовать ViewStub, который сможет создать эти View при необходимости.
Я не буду подробно объяснять работу этого механизма, чтобы не перегружать урок. Покажу только, как с ним использовать биндинг.
Рассмотрим пример. У нас есть экран, на котором мы отображаем employee.name.
main_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="employee"
type="ru.startandroid.applicationtest003.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}" />
<ViewStub
android:id="@+id/employeeAddressStub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout="@layout/employee_address"/>
</LinearLayout>
</layout>
А вот адрес нам надо отображать крайне редко (по нажатию на какую-то кнопку или если он не пустой). И мы решаем, что будем использовать ViewStub. В его параметре layout указываем имя layout файла (employee_address), который должен будет отображаться, когда мы попросим об этом ViewStub.
Layout файл employee_address.xml - это обычный layout файл с биндингом:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="employee"
type="ru.startandroid.applicationtest003.data.Employee" />
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.address}" />
</LinearLayout>
</layout>
Код биндинга:
employee = new Employee(1, "John Smith", "London", 10000);
binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
binding.setEmployee(employee);
binding.employeeAddressStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub viewStub, View view) {
EmployeeAddressBinding binding = DataBindingUtil.bind(view);
binding.setEmployee(employee);
}
});
Первые три строки понятны: создаем Employee, получаем биндинг и передаем Employee в биндинг. Этот код сработает для основного layout, и employee.name будет отображен на экране.
Но layout, указанный в ViewStub, мы будем создавать позже (если вообще будем). И после того, как мы его создадим, нам надо будет создать для него отдельный биндинг и передать в этот биндинг данные (Employee).
Используем OnInflateListener, который сработает при создании layout в ViewStub. В onInflate мы получим View, созданный из employee_address.xml. Для этого View методом DataBindingUtil.bind создаем биндинг и передаем туда Employee. Таким образом адрес попадет в TextView.
Обработчик готов. Но сработает он только тогда, когда мы надумаем создать layout из ViewStub. А сделать это можно так:
if (!binding.employeeAddressStub.isInflated()) {
binding.employeeAddressStub.getViewStub().inflate();
}
Проверяем, что из этого ViewStub еще не создавался layout, и запускаем процесс создания.
binding.employeeAddressStub возвращает нам не сам ViewStub, а обертку над ним. Это сделано потому, что ViewStub будет заменен созданным layout и его больше не будет в иерархии View. Соответственно, к нему нельзя будет обратиться. А обертка продолжит жить.
RecyclerView
Рассмотрим пример, как использовать биндинг для элементов списка RecylerView
layout элемента списка выглядит так
employee_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="employee"
type="ru.startandroid.applicationtest003.data.Employee"/>
</data>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employee.name}" />
</LinearLayout>
</layout>
Будем выводить только имя работника
Создаем Holder:
class EmployeeHolder extends RecyclerView.ViewHolder {
EmployeeItemBinding binding;
public EmployeeHolder(EmployeeItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Employee employee) {
binding.setEmployee(employee);
binding.executePendingBindings();
}
}
Обычно холдеры, которые мы пишем, в свой конструктор ожидают получить View. Но в случае использования биндингом, нам нужен будет объект EmployeeItemBinding - биндинг класс для layout элемента списка.
Методом getRoot мы получим View из этого биндинга и передадим его в конструктор суперкласса.
В метод bind мы будем получать Employee и нам останется только передать его в биндинг, который за нас раскидает значения по View. Метод executePendingBindings используется, чтобы биндинг не откладывался, а выполнился как можно быстрее. Это критично в случае с RecyclerView.
Класс адаптера:
class EmployeeAdapter extends RecyclerView.Adapter<EmployeeHolder> {
private List<Employee> items = new LinkedList<>();
public void setData(List<Employee> data) {
items.clear();
items.addAll(data);
}
@NonNull
@Override
public EmployeeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
EmployeeItemBinding binding = DataBindingUtil.inflate(inflater, R.layout.employee_item, parent, false);
return new EmployeeHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull EmployeeHolder holder, int position) {
holder.bind(items.get(position));
}
@Override
public int getItemCount() {
return items.size();
}
}
В onCreateViewHolder получаем LayoutInflater. Затем методом DataBindingUtil.inflate создаем биндинг, который будет содержать в себе View, созданный из R.layout.employee_item. Этот биндинг передаем в конструктор холдера.
В onBindViewHolder получаем employee из items и передаем его в bind метод холдера, тем самым запуская биндинг, который заполнит View данными из employee.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

