Android Data Binding. Код у layout. Доступ до View
Продовжуємо говорити про 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 + ")") }' />
Біндінг вміє працювати і з колекціями. Наприклад, якщо в 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();