Android Data Binding. Обробка подій
За допомогою біндингу ми можемо вішати обробники на події View. Є два способи це зробити, давайте розглянемо їх.
Посилання на метод
Розглянемо приклад з onClick. Припустимо, у нас на екрані, який відображає дані щодо працівника (Employee), є кнопка Delete, і ми хочемо присвоїти їй onClick обробник.
Створюємо свій клас обробник:
public class MyHandler {
public void onDelete(View view) {
// ...
}
}
Він не обов'язково має наслідувати OnClickListener. Але його метод має бути public і мати ті самі параметри, що й метод OnClickListener.onClick(View 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.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)}"/>
Тут використовується лямбда. На вхід нам пропонуються ті самі параметри, що і в методі інтерфейсу стандартного обробника, тобто 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 як реалізацію.