В этом уроке рассмотрим примеры использование Android Data Binding с include, ViewStub и RecyclerView

 

 


Полный список уроков курса:


 

 

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, Kotlin, RxJava, Dagger, Тестирование 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

- тут можно посмотреть, над какими уроками я сейчас работаю, и о чем будут следующие уроки 




Language

Социальные сети

 

Telegram канал



Android чат в Telegram



Группа ВКонтакте



Страница в Facebook