Введення у фрагменти

Організація додатка на основі декількох activity не завжди може бути оптимальною. Світ ОС Android досить сильно фрагментований і складається з багатьох пристроїв. І якщо для мобільних апаратів із невеликими екранами взаємодія між різними activity має доволі непоганий вигляд, то на великих екранах - планшетах, телевізорах - вікна activity мали б не дуже гарний вигляд завдяки великому розміру екрана. Власне тому і з'явилася концепція фрагментів.

Фрагмент являє собою шматочок візуального інтерфейсу застосунку, який може використовуватися повторно і багаторазово. У фрагмента може бути власний файл layout, у фрагментів є свій власний життєвий цикл. Фрагмент існує в контексті activity і має свій життєвий цикл, поза activity відокремлено він існувати не може. Кожна activity може мати кілька фрагментів.

Для початку роботи з фрагментами створимо новий проєкт із порожньою MainActivity. І спочатку створимо перший фрагмент. Але відразу варто зазначити, що не вся функціональність фрагментів за замовчуванням може бути доступною в проєкті, оскільки розташовується в окремій бібліотеці - AndroidX Fragment library. І спочатку необхідно підключити до проекту цю бібліотеку у файлі build.gradle.

Знайдемо в ньому секцію dependencies, яка за замовчуванням має приблизно такий вигляд:

dependencies {
 
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

На її початок додамо рядок

implementation "androidx.fragment:fragment:1.3.6"

Тобто в моєму випадку вийде

dependencies {
 
    implementation "androidx.fragment:fragment:1.3.6"
     
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

І потім натиснемо на з'явившуся посилання "Sync Now".

Фактично фрагмент — це звичайний клас Java, який успадковується від класу Fragment. Однак, як і клас Activity, фрагмент може використовувати xml-файли layout для визначення графічного інтерфейсу. І таким чином, ми можемо додати окремо клас Java, який представляє фрагмент, та файл xml для зберігання в ньому розмітки інтерфейсу, яку буде використовувати фрагмент.

Отже, додамо до папки res/layout новий файл fragment_content.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">
     
    <Button
        android:id="@+id/updateButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Оновити"
        app:layout_constraintBottom_toTopOf="@+id/dateTextView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
     
    <TextView
        android:id="@+id/dateTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Привіт з Фрагмента"
        android:textSize="28sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/updateButton" />
     
</androidx.constraintlayout.widget.ConstraintLayout>

Фрагменти містять ті ж елементи керування, що й activity. Зокрема, тут визначено кнопку та текстове поле, які складатимуть інтерфейс фрагмента.

Тепер створимо сам клас фрагмента. Для цього додамо до однієї папки з MainActivity новий клас. Для цього натиснемо на папку правою кнопкою миші та виберемо в меню New -> Java Class. Назвемо новий клас ContentFragment та визначимо в ньому наступний вміст:

package com.example.fragmentapp;

import androidx.fragment.app.Fragment;

public class ContentFragment extends Fragment {

    public ContentFragment(){
        super(R.layout.fragment_content);
    }
}

Клас фрагмента повинен успадковуватися від класу Fragment.

Щоб вказати, що фрагмент буде використовувати певний xml-файл layout, ідентифікатор ресурсу layout передається в виклик конструктора батьківського класу (тобто класу Fragment).

Весь проєкт матиме такий вигляд:

Додавання фрагмента в Activity

Для використання фрагмента додамо його в MainActivity. Для цього змінемо файл activity_main.xml, який визначає інтерфейс для MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:name="com.example.fragmentapp.ContentFragment" />

Для додавання фрагмента застосовується елемент FragmentContainerView. По суті, FragmentContainerView представляє об'єкт View, який розширює клас FrameLayout і призначений спеціально для роботи з фрагментами. Власне, крім фрагментів, він більше нічого не може містити.

Його атрибут android:name вказує на ім'я класу фрагмента, який буде використовуватися. У моєму випадку — повне ім'я класу фрагмента з урахуванням пакета com.example.fragmentapp.ContentFragment.

Код класу MainActivity залишається тим самим, що й при створенні проєкту:

package com.example.fragmentapp;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Якщо ми запустимо застосунок, то побачимо фактично той самий інтерфейс, який ми могли б зробити і через activity, тільки в даному випадку інтерфейс буде визначено у фрагменті.

Варто зазначити, що Android Studio представляє готовий шаблон для додавання фрагмента. Власне скористаємося цим способом.

Для цього натиснемо на папку, де міститься клас MainActivity, правою кнопкою миші і в меню, що з'явиться, виберемо New -> Fragment -> Fragment(Blank):

Цей шаблон запропонувати вказати клас фрагмента і назву файлу пов'язаного з ним класу розмітки інтерфейсу.

Додавання логіки до фрагмента

Фрагмент визначає кнопку. Тепер додамо до цієї кнопки певну дію. Для цього змінемо клас ContentFragment:

package com.example.fragmentapp;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.util.Date;

public class ContentFragment extends Fragment {

    public ContentFragment(){
        super(R.layout.fragment_content);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button updateButton = view.findViewById(R.id.updateButton);
        TextView updateBox = view.findViewById(R.id.dateTextView);

        updateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String curDate = new Date().toString();
                updateBox.setText(curDate);
            }
        });
    }
}

Тут переозначено метод onViewCreated класу Fragment, який викликається після створення об'єкта View для візуального інтерфейсу, що представляє цей фрагмент. Створений об'єкт View передається як перший параметр. Далі ми можемо отримати конкретні елементи управління в рамках цього об'єкта View, зокрема TextView і Button, і виконати з ними певні дії. У даному випадку в обробнику натискання кнопки в текстовому полі відображається поточна дата.

Додавання фрагмента в коді

Крім визначення фрагмента у xaml-файлі інтерфейсу, ми можемо додати його динамічно в activity.

Для цього змінимо файл activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

І також змінимо клас MainActivity:

package com.example.fragmentapp;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container_view, ContentFragment.class, null)
                    .commit();
        }
    }
}

Метод getSupportFragmentManager() повертає об'єкт FragmentManager, який керує фрагментами.

Об'єкт FragmentManager за допомогою методу beginTransaction() створює об'єкт FragmentTransaction.

FragmentTransaction виконує два методи: add() і commit(). Метод add() додає фрагмент: add(R.id.fragment_container_view, new ContentFragment()) — першим аргументом передається ресурс розмітки, в який потрібно додати фрагмент (це визначений в activity_main.xml елемент androidx.fragment.app.FragmentContainerView). І метод commit() підтверджує і завершує операцію додавання.

Кінцевий результат такого додавання фрагмента буде таким самим, як і при явному визначенні фрагмента через елемент FragmentContainerView в розмітці інтерфейсу.