Ограничение доступа
Ролевой механизм
Загрузка модулей410013796724260
|
Вернуться к «Разработчикам Android»
Пример использования фрагментовФрагменты облегчают построение адаптивного интерфейса из «шаблонов», которые можно динамически отобразить на экране устройства, скрыть или заменить другим фрагментом. Рассмотрим пример использования двух фрагментов. Сначала рассмотрим формирование интерфейса из активности и фрагментов. После этого рассмотрим взаимодействие между отдельными частями интерфейса, т.е. между активностью и фрагментами. Для работы с фрагментами создадим в Android Studio модуль (пример) fragment_01, представленный на следующем скриншоте. Модуль включает активность MainActivity и два фрагмента Fragment1/Fragment2, расположенные в пакете com.example.fragment. Шаблоны интерфейсов активности и фрагментов расположены в поддиректории res/layout (main_activity.xml, fragment1.xml, fragment2.xml). Ресурсный файл res/values/strings.xml содержит набор строк для определения заголовков примера и компонентов.
Сразу же необходимо отметить, что не вся функциональность фрагментов может быть доступна в модуле, поскольку используемая AndroidX Fragment Library располагается в отдельной библиотеке. Сначала необходимо подключить к модулю эту библиотеку в файле build.gradle.
...
dependencies {
implementation "androidx.fragment:fragment:1.4.1"
...
}
Шаблоны интерфейсаИнтерфейс примера разделим условно на 3 части : активность, статический фрагмент и динамический фрагмент. Стастический и динамический фрагменты, конечно же, включены в активность и они занимают часть общего интерфейса. Чтобы визуально можно было бы легко различать части интерфейса, раскрасим их (background) в разные цвета. Каждая часть интерфейса будет включать текстовый компонент типа TextView и кнопку. Текстовые компоненты используем для отображения сообщений, а кнопки для передачи этих сообщений. Сообщения будем передавать как из активности во фрагменты, так и из фрагментов в активность, а также из одного фрагмента в другой фрагмент. Ниже в листинге main_activity.xml описан интерфейс (шаблон) главной активности. Комментарии к шаблону после листинга. Листинг main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/activity_text"
android:textStyle="bold">
</TextView>
<Button
android:id="@+id/btnFind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/btn_activity"
android:textAllCaps="false">
</Button>
<fragment
android:id="@+id/fragment1"
android:name="com.example.fragment.Fragment1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:layout="@layout/fragment1">
</fragment>
<FrameLayout
android:id="@+id/fragment2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
</FrameLayout>
</LinearLayout>
Интерфейс активности включает текстовую метку TextView и кнопку Button. Тексты меток и кнопок (android:text) определены в ресурсном файле res/values/strings.xml. К кнопке подключен обработки события android:onClick. Статический фрагмент в секции <fragment> содержит полное наименование класса (android:name) и шаблон интерфейса фрагмента, представленный атрибутом tools:layout. Динамический fragment2 определен компонентом типа контейнер FrameLayout, в который в режиме run-time будет загружен Fragment2. Таким образом, шаблон активности main_activity.xml включает метку с кнопкой, статический fragment1 и контейнер FrameLayout для динамической загрузки фрагмента. Каждый компонент интерфейса имеет идентификатор android:id. Шаблоны фрагментовШаблоны фрагментов fragment1 и fragment2 имеют одинаковый контент/интерфейс и также, как и шаблон активности, включают метку типа TextView и кнопку. Отличия фрагментов касаются только цвета фона android:background. Необходимо отметить, что к кнопкам не подключены обработчики (onClick); подключение обработчиков событий нажатия кнопок выполнено в java-коде. Ниже представлен листинг шаблона fragment1.xml. Листинг fragment1.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#8822aaff"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/frag1_text"
android:textStyle="bold">
</TextView>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_fragment"
android:textAllCaps="false">
</Button>
</LinearLayout>
На следующем скриншоте представлен интерфейс примера после старта, в котором наглядно обозначены 3 части интерфейса различными фоновыми цветами.
Контент ресурсного файла strings.xml, строки которого используются в метках и заголовках кнопок, представлен ниже в свернутом виде. Листинг strings.xml
<resources>
<string name="app_name">fragment_01</string>
<string name="activity_text">MainActivity</string>
<string name="frag1_text">Static Fragment</string>
<string name="frag2_text">Dynamic Fragment</string>
<string name="btn_fragment">Button</string>
<string name="btn_activity">Button Activity</string>
</resources>
Класс активностиКласс акивности использует шаблон R.layout.main_activity для формирования интерфейса. При динамической загрузке фрагмента в интерфейс примера используются классы FragmentManager и FragmentTransaction. Метод добавления фрагмента в интерфейс FragmentTransaction.add в качестве параметров получает значение идентификатора R.id.fragment2 (расположение в интерфейсе/шаблоне) и объект реализации Fragment2. В методе onClick, вызываемый при нажатии на кнопку активности, выводится отладочное сообщение Logcat с тегом LOG_TAG. Листинг активности представлен ниже в свернутом виде. Листинг MainActive.java
package com.example.fragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity
{
final String LOG_TAG = "DEVELOPMENT";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
// Классы управления фрагментами
FragmentManager fm;
FragmentTransaction ft;
// Динамическая загрузка fragment2
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
Fragment fr = new Fragment2();
ft.add(R.id.fragment2, fr);
ft.commit();
}
public void onClick(View v)
{
Log.d(LOG_TAG, "Button click in Activity");
}
}
Ниже представлен альтернативный код динамической загрузки фрагмента в интерфейс. В этом коде, как Вы видите, отсутствуют переменные классов фрагментов.
// Альтернативная запись загрузки динамического фрагмента
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment2, new Fragment2())
.commitNow();
Классы фрагментовКлассы фрагментов также, как и их интерфейсные шаблоны (fragment[1|2].xml), идентичны. Они наследуют свойства и методы базового класса androidx.fragment.app.Fragment. Переопределенный метод onCreateView формирует интерфейс фрагмента. К кнопке подключается обработчик события onClick, который выводит отладочное сообщение. Листинг Fragment2.java
package com.example.fragment;
import android.os.Bundle;
import android.util.Log;
import android.content.Context;
import android.widget.Button;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import androidx.fragment.app.Fragment;
public class Fragment2 extends Fragment {
final String LOG_TAG = "DEVELOPMENT";
//-----------------------------------------------------
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
View v =inflater.inflate(R.layout.fragment2, null);
Button button=(Button) v.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOG_TAG, "Button click in Fragment2");
}
});
return v;
}
}
Отличие кода Fragment1 от Fragment2 связано с переопределением всех методов жизненного цикла фрагмента, в которых выводятся отладочные сообщения. Листинг Fragment1 представлен ниже в свернутом виде. Листинг Fragment1.java
package com.example.fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.widget.Button;
import androidx.fragment.app.Fragment;
public class Fragment1 extends Fragment {
final String LOG_TAG = "DEVELOPMENT";
//-----------------------------------------------------
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment1, null);
Button button=(Button) v.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Log.d(LOG_TAG,"Button click in Fragment1");
}
});
Log.d(LOG_TAG, "fragment onCreateView");
return v;
}
//-----------------------------------------------------
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(LOG_TAG, "fragment onAttach");
}
//-----------------------------------------------------
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(LOG_TAG, "fragment onCreate");
}
//-----------------------------------------------------
@Override
public void onViewCreated(View view,
Bundle savedInstanceState)
{
super.onStart();
Log.d(LOG_TAG, "fragment onViewCreated");
}
//-----------------------------------------------------
@Override
public void onStart() {
super.onStart();
Log.d(LOG_TAG, "fragment onStart");
}
//-----------------------------------------------------
@Override
public void onResume() {
super.onResume();
Log.d(LOG_TAG, "fragment onResume");
}
//-----------------------------------------------------
@Override
public void onPause() {
super.onPause();
Log.d(LOG_TAG, "fragment onPause");
}
//-----------------------------------------------------
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
Log.d(LOG_TAG, "fragment saveInstanceState");
}
//-----------------------------------------------------
@Override
public void onStop() {
super.onStop();
Log.d(LOG_TAG, "fragment onStop");
}
//-----------------------------------------------------
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(LOG_TAG, "fragment onDestroyView");
}
//-----------------------------------------------------
@Override
public void onDestroy() {
super.onDestroy();
Log.d(LOG_TAG, "fragment onDestroy");
}
//-----------------------------------------------------
@Override
public void onDetach() {
super.onDetach();
Log.d(LOG_TAG, "fragment onDetach");
}
}
Старт примераПосле старта примера (скриншот выше) и нажатии на кнопки мы увидим следующие отладочные сообщения в консоли (дата и время убраны). Чтобы вызвать callback-методы жизненного цикла, связанные с остановкой и выгрузкой фрагмента, можно выполнить рестарт приложения. com.example.fragment DEVELOPMENT: fragment onAttach com.example.fragment DEVELOPMENT: fragment onCreate com.example.fragment DEVELOPMENT: fragment onCreateView com.example.fragment DEVELOPMENT: fragment onViewCreated com.example.fragment DEVELOPMENT: fragment onStart com.example.fragment DEVELOPMENT: fragment onResume com.example.fragment DEVELOPMENT: Button click in Activity com.example.fragment DEVELOPMENT: Button click in Fragment1 com.example.fragment DEVELOPMENT: Button click in Fragment2 // рестарт приложения com.example.fragment DEVELOPMENT: fragment onPause com.example.fragment DEVELOPMENT: fragment onStop com.example.fragment DEVELOPMENT: fragment saveInstanceState com.example.fragment DEVELOPMENT: fragment onDestroyView com.example.fragment DEVELOPMENT: fragment onDestroy com.example.fragment DEVELOPMENT: fragment onDetach Взаимодействие активности и фрагментовПосле того, как интерфейс определен и описан, можно приступить к взаимодействию активности и фрагментов. Будем получать доступ к меткам активности/фрагментов из различных объектов и вносить изменения в их текст. Доступ к фрагментам из активностиНиже представлен расширенный код метода onClick активности. В исходном варианте в методе выводилось в консоль сообщение о нажатии на кнопку (последняя строка). В обновленном варианте определяются переменные фрагментов frgm1/frgm2 с использованием менеджера фрагментов getSupportFragmentManager и его метода findFragmentById, который в качестве параметра должен получить идентификатор фрагмента. Мы помним, что в шаблоне активности main_activity.xml статический фрагмент и контейнер фрагмента имеют идентификаторы (android:id). Текстовые компоненты TextView фрагментов определяются методами getView и findViewById. После того, как компонент метки определен, её текстовое значение изменяется. Метод onClick активности MainActive.java
public void onClick(View v)
{
TextView tv ;
Fragment frgm1;
Fragment frgm2;
frgm1 = getSupportFragmentManager()
.findFragmentById(R.id.fragment1);
tv = ((TextView) frgm1.getView()
.findViewById(R.id.textView));
tv.setText("Access to Fragment 1 from Activity");
//------------
frgm2 = getSupportFragmentManager()
.findFragmentById(R.id.fragment2);
tv = ((TextView) frgm2.getView()
.findViewById(R.id.textView));
tv.setText("Access to Fragment 2 from Activity");
Log.d(LOG_TAG, "Button click in Activity");
}
Доступ к Activity из фрагментаПопробуем из фрагмента достучаться до Activity. Метод фрагмента getActivity возвращает связанную с ним активность. После того, как активность получена можно получить доступ к визуальным компонентам View активности. Для реализации данного подхода внесем изменения в метод onClick первого фрагмента, как это представлено в следующем листинге. Метод onClick фрагмента Fragment1.java
public void onClick(View v)
{
TextView tv = (TextView)getActivity()
.findViewById(R.id.textView);
tv.setText("Access from Fragment");
Log.d(LOG_TAG, "Button click in Fragment1");
}
Взаимодействие фрагментов между собойКак могут "достучаться" друг до друга два фрагмента, если они ничего друг о друге не знают? Но они хорошо "знакомы" с активностью, с которой они ассоциированы. Вот через эту активность и могут друг другу "передавать привет" фрагменты. Для этого в одном из фрагментов определяется интерфейс с одним или несколькими методами. Активность же должна имплементировать данный интерфейс, переопределяя его методы. Вызов соответствующего метода активности позволяет через неё отправить сообщение другому фрагменту. Схема такого подхода известная и распространенная. Таким образом, можно организовать навигацию, когда один из фрагментов используется в качестве меню, а второй фрагмент отображает связанный с пунктами меню контенты; активность же выступает в роли посредника. Ниже представлен листинг модифицированного Fragment2 с выделенными изменениями. Как видим из кода, в класс Fragment2 добавлены интерфейс onEventListener и метод onAttach, вызываемый при "присоединении" фрагмента к активности. В методе onAttach сначала определяется активность activity, после чего интерфейсная переменная eventListener (родитель). Активность должна реализовать интерфейс onEventListener с методом sendText. При нажатии на кнопку фрагмента (метод onClick) вызывается родительский метод активности sendText. Всё довольно просто и прозрачно. Листинг Fragment2.java
package com.example.fragment;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
import android.view.View.OnClickListener;
import android.widget.Button;
import androidx.fragment.app.Fragment;
public class Fragment2 extends Fragment
{
onEventListener eventListener = null;
final String LOG_TAG = "DEVELOPMENT";
//-----------------------------------------------------
public interface onEventListener
{
public void sendText(String s);
}
//-----------------------------------------------------
@Override
public void onAttach(Context context)
{
super.onAttach(context);
AppCompatActivity activity = null;
if (context instanceof AppCompatActivity)
activity = (AppCompatActivity) context;
try {
eventListener = (onEventListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()+
" must implement onEventListener");
}
}
//-----------------------------------------------------
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View v =inflater.inflate(R.layout.fragment2, null);
Button button =(Button)v.findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
eventListener.sendText("Text to Fragment1");
Log.d(LOG_TAG, "Button click in Fragment2");
}
});
return v;
}
}
В заключении мы должны модифицировать активность, чтобы она могла передать сообщение из второго фрагмента первому. Для этого в описание класса вносим имплементацию интерфейса (Fragment2.onEventListener) и добавляем метод sendText. А дальше по знакомому сценарию в методе определяем фрагмент и меняем текст метки TextView. Методы активности onCreate и onClick остались без изменений. Листинг MainActivity.java
public class MainActivity extends AppCompatActivity
implements Fragment2.onEventListener
{
final String LOG_TAG = "DEVELOPMENT";
//-----------------------------------------------------
@Override
public void sendText(String s)
{
Fragment frgm1;
frgm1 = getSupportFragmentManager()
.findFragmentById(R.id.fragment1);
TextView tv;
tv = (TextView)frgm1.getView()
.findViewById(R.id.textView);
tv.setText("Text from Fragment 2 : " + s);
}
//-----------------------------------------------------
@Override
protected void onCreate(Bundle savedInstanceState)
{
. . .
}
//-----------------------------------------------------
public void onClick(View v)
{
. . .
}
}
|
