Организация приложения на основе нескольких 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="Hello from Fragment" 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).
Весь проект будет выглядеть следующим образом:
Для использования фрагмента добавим его в 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
в разметке интерфейса.