Взаємодія між фрагментами
Одна activity може використовувати кілька фрагментів, наприклад, з одного боку список, а з іншого - детальний опис обраного елемента списку. У такій конфігурації activity використовує два фрагменти, які між собою повинні взаємодіяти. Розглянемо базові принципи взаємодії фрагментів у додатку.
Створимо новий проєкт із порожньою MainActivity. Далі створимо розмітку layout для фрагментів. Нехай у нас у додатку буде два фрагменти. Додамо в папку res/layout новий xml-файл fragment_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ListView
android:id="@+id/countriesList"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ListView>
</androidx.constraintlayout.widget.ConstraintLayout>
Тут визначено елемент ListView для виведення списку об'єктів.
І також додамо для іншого фрагмента файл розмітки fragment_detail.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<TextView
android:id="@+id/detailsText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Не вибрано"
android:textSize="22sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
Обидва фрагменти будуть максимально простими: один буде містити список, а другий — текстове поле. Логіка програми буде така: при виборі елемента в списку в одному фрагменті вибраний елемент повинен відобразитися в текстовому полі, яке знаходиться в другому фрагменті.
Далі додамо в проект в одну папку з MainActivity власне класи фрагментів. Додамо новий клас ListFragment з наступним вмістом:
package com.example.fragmentapp;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.fragment.app.Fragment;
public class ListFragment extends Fragment {
interface OnFragmentSendDataListener {
void onSendData(String data);
}
private OnFragmentSendDataListener fragmentSendDataListener;
String[] countries = { "Бразилія", "Аргентина", "Колумбія", "Чилі", "Уругвай"};
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
fragmentSendDataListener = (OnFragmentSendDataListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " повинен реалізовувати інтерфейс OnFragmentInteractionListener");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_list, container, false);
// отримуємо елемент ListView
ListView countriesList = view.findViewById(R.id.countriesList);
// створюємо адаптер
ArrayAdapter<String> adapter = new ArrayAdapter(getContext(), android.R.layout.simple_list_item_1, countries);
// встановлюємо для списку адаптер
countriesList.setAdapter(adapter);
// додаємо для списку слухач
countriesList.setOnItemClickListener(new AdapterView.OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
// отримуємо вибраний елемент
String selectedItem = (String)parent.getItemAtPosition(position);
// Посилаємо дані Activity
fragmentSendDataListener.onSendData(selectedItem);
}
});
return view;
}
}
Фрагменти не можуть напряму взаємодіяти між собою. Для цього треба звертатися до контексту, в якості якого виступає клас Activity. Для звернення до activity, зазвичай, створюється вкладений інтерфейс. У цьому випадку він називається OnFragmentSendDataListener з одним методом.
interface OnFragmentSendDataListener {
void onSendData(String data);
}
private OnFragmentSendDataListener fragmentSendDataListener;
Але щоб взаємодіяти з іншим фрагментом через activity, нам треба прикріпити поточний фрагмент до activity. Для цього в класі фрагмента визначений метод onAttach(Context context). В ньому відбувається установка об'єкта OnFragmentSendDataListener:
fragmentSendDataListener = (OnFragmentSendDataListener) context;
При обробці натискання на елемент в списку ми можемо відправити Activity дані про вибраний об'єкт:
String selectedItem = (String)parent.getItemAtPosition(position);
fragmentSendDataListener.onSendData(selectedItem);
Таким чином, при виборі об'єкта в списку MainActivity отримає вибраний об'єкт.
Тепер визначимо клас для другого фрагмента. Назвемо його DetailFragment:
package com.example.fragmentapp;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
public class DetailFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_detail, container, false);
}
// оновлення текстового поля
public void setSelectedItem(String selectedItem) {
TextView view = getView().findViewById(R.id.detailsText);
view.setText(selectedItem);
}
}
Задача цього фрагмента — виведення певної інформації. Оскільки він не повинен передавати жодну інформацію іншому фрагменту, тут ми можемо обмежитися лише перевизначенням методу onCreateView(), який в якості візуального інтерфейсу встановлює розмітку з файлу fragment_detail.xml.
Але для імітації взаємодії між двома фрагментами, тут також визначений метод setSelectedItem(), який оновлює текст у текстовому полі.
У підсумку вийде така структура:
Тепер змінимо файл розмітки activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<androidx.fragment.app.FragmentContainerView
android:id="@+id/listFragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:name="com.example.fragmentapp.ListFragment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/detailFragment"
app:layout_constraintRight_toRightOf="parent"/>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detailFragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:name="com.example.fragmentapp.DetailFragment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/listFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
За допомогою двох елементів FragmentContainerView у MainActivity додаються два вище визначені фрагменти.
І наприкінці змінимо код MainActivity:
package com.example.fragmentapp;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity implements ListFragment.OnFragmentSendDataListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onSendData(String selectedItem) {
DetailFragment fragment = (DetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.detailFragment);
if (fragment != null)
fragment.setSelectedItem(selectedItem);
}
}
Для взаємодії фрагмента ListFragment з іншим фрагментом через MainActivity необхідно, щоб ця activity реалізовувала інтерфейс OnFragmentSendDataListener. Для цього реалізуємо метод onSendData(), який отримує фрагмент DetailFragment і викликає в ньому метод setSelectedItem().
В результаті вийде, що при виборі елемента в списку у фрагменті ListFragment буде спрацьовувати слухач списку, а саме його метод onItemClick(AdapterView<?> parent, View v, int position, long id), який викликає метод fragmentSendDataListener.onSendData(selectedItem);. fragmentSendDataListener встановлюється як MainActivity, тому при цьому буде викликаний метод setSelectedItem у фрагмента DetailFragment. Таким чином, відбудеться взаємодія між двома фрагментами.
Якщо ми запустимо проект, то на екран будуть виведені обидва фрагменти, які зможуть взаємодіяти між собою.

Однак поки що фрагменти однаково виводяться в одній activity як в альбомній, так і в портретній орієнтації незалежно від пристрою. Тому оптимізуємо застосунок.