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 як реалізацію.