В этом уроке рассмотрим как обрабатывать события 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. Просмотр задачи
С помощью биндинга мы можем вешать обработчики на события View. Есть два способа это сделать, давайте рассмотрим их.
Ссылка на метод
Рассмотрим пример с onClick. Допустим, у нас на экране, который отображает данные по работнику (Employee), есть кнопка Delete и мы хотим присвоить ей onClick обработчик.
Создаем свой класс обработчик:
public class MyHandler { public void onDelete(View view) { // ... } }
Он не обязательно должен наследовать OnClickListener. Но его метод должен быть public и иметь те же параметры, что и метод OnClickListener.onClick(View view), т.е. должен быть один параметр типа View. Имя метода может быть любым.
Прописываем этот обработчик, как variable в layout.
<data> <variable name="handler" type="ru.startandroid.application.MyHandler" /> ... </data>
В onClick кнопки ссылаемся на его метод onDelete:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/delete" android:onClick="@{handler::onDelete}"/>
Осталось создать объект MyHandler и передать его в биндинг:
MyHandler myHandler = new MyHandler(); binding.setHandler(myHandler);
По нажатию на кнопку Delete будет вызван метод onDelete объекта myHandler.
Если при попытке настроить обработчик в биндинге вы получаете подобную ошибку:
Listener class android.view.View.OnClickListener with method onClick did not match signature of any method handler::onDelete
внимательно проверьте, что модификаторы доступа и параметры метода в вашем обработчике такие же, что и в методе интерфейса стандартного обработчика. В случае с onClick - это OnClickListener.
Вызов метода
Если в первом способе мы просто указывали биндингу, какой метод обработчика вызвать, то во втором способе мы просто сами будем вызывать этот метод. Этот способ более гибкий, т.к. метод нашего обработчика не обязан иметь те же параметры, что и метод интерфейса стандартного обработчика.
Рассмотрим снова пример с onClick. Создаем обработчик.
public class MyHandler { public void onDelete(Employee employee) { // ... } }
В метод onDelete мы планируем получать не View, как в примере раньше, а объект Employee.
MyHandler так же, как и ранее, прописываем в variable и передаем в binding.
В onClick кнопки пишем вызов
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/delete" android:onClick="@{(view) -> handler.onDelete(employee)}"/>
Здесь используетcя лямбда. На вход нам предлагаются те же параметры, что и в методе интерфейса стандартного обработчика, т.е. view из OnClickListener.onClick(View view). Но мы не используем этот параметр. В метод onDelete мы передаем employee, который у нас описан, как один из variable в layout.
В результате по нажатию на кнопку, биндинг предоставит нам View, на которое было нажатие. Но мы его проигнорируем, возьмем у биндинга объект Employee и отправим в handler.onDelete.
Биндинг дает нам возможность не писать параметры в лямбде, если они нам не нужны. Т.е. можно сделать так:
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/delete" android:onClick="@{() -> handler.onDelete(employee)}"/>
Таким образом биндинг поймет, что его View нам не нужно, и не будет его передавать. Имейте ввиду, что если в стандартном обработчике несколько параметров, то вы можете указать либо все параметры, либо ни одного.
Чтобы закрепить тему, давайте рассмотрим пример с CheckBox. Например, на экране с данными по работнику есть чекбокс Enabled, который включает/выключает работника.
В обработчике создаем метод,
public class MyHandler { public void onEnabled(Employee employee, boolean enabled) { // ... } }
Будем получать объект Employee и состояние чекбокса.
В onCheckedChanged пишем вызов метода нашего обработчика.
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enabled" android:onCheckedChanged="@{(view, checked) -> handler.onEnabled(employee, checked)}" />
В лямбде указываем параметры, которые пришли бы нам в стандартном обработчике OnCheckedChangeListener.onCheckedChanged(CompoundButton compoundButton, boolean checked).
Параметр view нам не понадобится, а вот checked передаем в метод вместе с employee.
Теперь по нажатию на чекбокс, биндинг будет вызывать метод onEnabled и передавать туда Employee объект и состояние чекбокса.
Рассмотрим еще несколько интересных моментов.
При вызове обработчика мы можем использовать условия.
Например, есть такой обработчик.
public class MyHandler { public void onEnabled(Employee employee) { // ... } public void onDisabled(Employee employee) { // ... } }
Мы можем в layout указать его методы следующим образом
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enabled" android:onCheckedChanged="@{(view, checked) -> checked ? handler.onEnabled(employee) : handler.onDisabled(employee)}"/>
Т.е. если чекбокс включен, то вызываем метод onEnabled, иначе - onDisabled.
Если в одном из случаев нам ничего не надо вызывать, то можно использовать void
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enabled" android:onCheckedChanged="@{(view, checked) -> checked ? handler.onEnabled(employee) : void}"/>
В биндиге по умолчанию есть переменная context, которую вы всегда можете использовать, если есть необходимость.
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enabled" android:onCheckedChanged="@{(view, checked) -> handler.onEnabled(employee, checked, context)}"/>
Значение переменной context получено вызовом метода getContext у корневого View вашего layout.
В этих примерах я создавал отдельный объект обработчика, но разумеется вы можете создавать интерфейсы. прописывать их в качестве variable в layout и использовать хоть само Activity в качестве реализации.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня