Как найти фрагмент активности

Фрагменты

Кот из фрагментов

Существует два основных подхода в использовании фрагментов.

Первый способ основан на замещении родительского контейнера. Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout. В коде контейнер замещается фрагментом. При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически. Также вам придётся обновлять ActionBar, если он зависит от фрагмента. Здесь показан такой пример.

Второй вариант — используются отдельные разметки для телефонов и планшетов, которые можно разместить в разных папках ресурсов. Например, если в планшете используется двухпанельная разметка с двумя фрагментами на одной активности, мы используем эту же активность для телефона, но подключаем другую разметку, которая содержит один фрагмент. Когда нам нужно переключиться на второй фрагмент, то запускаем вторую активность.

Второй подход является наиболее гибким и в целом предпочтительным способом использования фрагментов. Активность проверяет в каком режиме (свои размеры) он запущен и использует разную разметку из ресурсов. Графически это выглядит следующим образом.

Fragment

Fragment

Основные классы

Сами фрагменты наследуются от androidx.fragment.app.Fragment. Существует подклассы фрагментов: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment и др. Не исключено, что число классов будет увеличиваться, например, появился ещё один класс MapFragment.

Для взаимодействия между фрагментами используется класс android.app.FragmentManager — специальный менеджер по фрагментам.

Как в любом офисе, спецманагер не делает работу своими руками, а использует помощников. Например, для транзакций (добавление, удаление, замена) используется класс-помощник android.app.FragmentTransaction.

Для сравнения приведу названия классов из библиотеки совместимости:

  • android.support.v4.app.FragmentActivity
  • android.support.v4.app.Fragment
  • android.support.v4.app.FragmentManager
  • android.support.v4.app.FragmentTransaction

Как видите, разница в одном классе, который я привёл первым. Он используется вместо стандартного Activity, чтобы система поняла, что придётся работать с фрагментами. На данный момент студия создаёт проект на основе ActionBarActivity, который является подклассом FragmentActivity.

В одном приложении нельзя использовать новые фрагменты и фрагменты из библиотеки совместимости.

В 2018 году Гугл объявила фрагменты из пакета androd.app устаревшими. Заменяйте везде на версию из библиотеки совместимости. В 2020 году уже используют пакет androidx.fragment.app.

В версии Support Library 27.1.0 появились новые методы requireActivity() и requireContext(), которые пригодятся при написании кода, когда требуется наличие активности и нужно избежать ошибки на null.

Общий алгоритм работы с фрагментами будет следующим:

У каждого фрагмента должен быть свой класс. Класс наследуется от класса Fragment или схожих классов, о которых говорилось выше. Это похоже на создание новой активности или нового компонента.

Также, как в активности, вы создаёте различные методы типа onCreate() и т.д. Если фрагмент имеет разметку, то используется метод onCreateView() — считайте его аналогом метода setContentView(), в котором вы подключали разметку активности. При этом метод onCreateView() возвращает объект View, который является корневым элементом разметки фрагмента.

Разметку для фрагмента можно создать программно или декларативно через XML.

Создание разметки для фрагмента ничем не отличается от создания разметки для активности. Вот отрывок кода из метода onCreateView():


public class FirstFragment extends Fragment implements OnClickListener {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.first_fragment,
                container, false);

        Button nextButton = (Button) view.findViewById(R.id.button_first);
        nextButton.setOnClickListener(this);

        return view;
    }

    // ...
}

Глядя на этот код, вы должные понять, что фрагмент использует разметку из файла res/layout/first_fragment.xml, которая содержит кнопку с идентификатором android:id=»@+id/button_first». Здесь также прослеживается сходство с подключением компонентов в активности. Обратите внимание, что перед методом findViewById() используется view, так как этот метод относится к компоненту, а не к активности, как мы обычно делали в программах, когда просто опускали имя активности. Т.е. в нашем случае мы ищем ссылку на кнопку не среди разметки активности, а внутри разметки самого фрагмента.

Нужно помнить, что в методе inflate() последний параметр должен иметь значение false в большинстве случаев.

FragmentManager

Класс FragmentManager имеет два метода, позволяющих найти фрагмент, который связан с активностью:

findFragmentById(int id)
Находит фрагмент по идентификатору
findFragmentByTag(String tag)
Находит фрагмент по заданному тегу

Методы транзакции

Мы уже использовали некоторые методы класса FragmentTransaction. Познакомимся с ними поближе

add()
Добавляет фрагмент к активности
remove()
Удаляет фрагмент из активности
replace()
Заменяет один фрагмент на другой
hide()
Прячет фрагмент (делает невидимым на экране)
show()
Выводит скрытый фрагмент на экран
detach() (API 13)
Отсоединяет фрагмент от графического интерфейса, но экземпляр класса сохраняется
attach() (API 13)
Присоединяет фрагмент, который был отсоединён методом detach()

Методы remove(), replace(), detach(), attach() не применимы к статичным фрагментам.

Перед началом транзакции нужно получить экземпляр FragmentTransaction через метод FragmentManager.beginTransaction(). Далее вызываются различные методы для управления фрагментами.

В конце любой транзакции, которая может состоять из цепочки вышеперечисленных методов, следует вызвать метод commit().


FragmentManager fragmentManager = getFragmentManager()
fragmentManager.beginTransaction()
    .remove(fragment1)
    .add(R.id.fragment_container, fragment2)
    .show(fragment3)
    .hide(fragment4)
    .commit();

Аргументы фрагмента

Фрагменты должны сохранять свою модульность и не должны общаться друг с другом напрямую. Если один фрагмент хочет докопаться до другого, он должен сообщить об этом своему менеджеру активности, а он уже передаст просьбу другому фрагменту. И наоборот. Это сделано специально для того, чтобы было понятно, что менеджер тут главный и он не зря зарплату получает. Есть три основных способа общения фрагмента с активностью.

  • Активность может создать фрагмент и установить аргументы для него
  • Активность может вызвать методы экземпляра фрагмента
  • Фрагмент может реализовать интерфейс, который будет использован в активности в виде слушателя

Фрагмент должен иметь только один пустой конструктор без аргументов. Но можно создать статический newInstance с аргументами через метод setArguments().


public class CatFragment extends Fragment {
    public static CatFragment newInstance(int someInt, String someString) {
        CatFragment catFragment = new CatFragment();
        Bundle args = new Bundle();
        args.putInt("someInt", someInt);
        args.putString("SomeString", someString);
        catFragment.setArguments(args);
        return catFragment;
    }
}

Доступ к аргументам можно получить в методе onCreate() фрагмента:


@Override
    public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       // Get back arguments
       int someInt = getArguments().getInt("someInt", 0);	
       String someString = getArguments().getString("someString", "");	
   }

Динамически загружаем фрагмент в активность.


FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
CatFragment catFragment = CatFragment.newInstance(5, "Васька");
ft.replace(R.id.your_placeholder, catFragment);
ft.commit();

Если активность должна выполнить какую-то операцию в фрагменте, то самый простой способ — задать нужный метод в фрагменте и вызвать данный метод через экземпляр фрагмента.


public class CatFragment extends Fragment {
    public void sayMeow(String word) {
        // do something in fragment
    }
}

Вызываем метод в активности:


public class MainActivity extends ActionBarActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CatFragment catFragment = (CatFragment) 
            getSupportFragmentManager().findFragmentById(R.id.fragmentCat);
        catFragment.sayMeow("Жрать!");
    }
}

Если фрагмент должен сообщить о своих действиях активности, то следует реализовать интерфейс.


import android.content.Context;
import android.support.v4.app.Fragment;
import android.view.View;

public class LinkListFragment extends Fragment {

    // Define the listener of the interface type
    // listener is the activity itself
    private OnLinkItemSelectedListener mListener;

    // Define the events that the fragment will use to communicate
    public interface OnLinkItemSelectedListener {
        public void onLinkItemSelected(String link);
    }

    // Store the listener that will have events fired once the fragment is attached
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (context instanceof OnLinkItemSelectedListener) {
            mListener = (OnLinkItemSelectedListener) context;
        } else {
            throw new ClassCastException(context.toString()
                    + " must implement MyListFragment.OnItemSelectedListener");
        }
    }

    // Now we can fire the event when the user selects something in the fragment
    public void onSomeClick(View v) {
        mListener.onLinkItemSelected("some link");
    }
}

В активности:


public class MainActivity extends ActionBarActivity implements
        LinkListFragment.OnLinkItemSelectedListener {
    DetailFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        fragment = (DetailFragment) getSupportFragmentManager()
               .findFragmentById(R.id.detailFragment);
    }
  
    // Now we can define the action to take in the activity when the fragment event fires
    @Override
    public void onLinkItemSelected(String link) {
        if (fragment != null && fragment.isInLayout()) {
            fragment.setText(link);
        }
    }
}

Управление стеком фрагментов

Фрагменты, как и активности, могут управляться кнопкой Back. Вы можете добавить несколько фрагментов, а потом через кнопку Back вернуться к первому фрагменту. Если в стеке не останется ни одного фрагмента, то следующее нажатие кнопки закроет активность.

Чтобы добавить транзакцию в стек, вызовите метод FragmentTransaction.addToBackStack(String) перед завершением транзакции (commit). Строковый аргумент — опциональное имя для идентификации стека или null. Класс FragmentManager имеет метод popBackStack(), возвращающий предыдущее состояние стека по этому имени.

Если вы вызовете метод addToBackStack() при удалении или замещении фрагмента, то будут вызваны методы фрагмента onPause(), onStop(), onDestroyView().

Когда пользователь нажимает на кнопку возврата, то вызываются методы фрагмента onCreateView(), onActivityCreated(), onStart() и onResume().

Рассмотрим пример реагирования на кнопку Back в фрагменте без использования стека. Активность имеет метод onBackPressed(), который реагирует на нажатие кнопки. Мы можем в этом методе сослаться на нужный фрагмент и вызвать метод фрагмента.


@Override
public void onBackPressed() {
    super.onBackPressed();
    // где-то ранее мы объявили фрагмент
    fragment1.backButtonWasPressed();
}

Теперь в классе фрагмента прописываем метод с нужным кодом.


public void backButtonWasPressed() {
	Toast.makeText(getActivity(), "Back button pressed", Toast.LENGTH_LONG)
			.show();
}

Более желательным вариантом является использование интерфейсов. В некоторых примерах с фрагментами такой приём используется.

Интеграция Action Bar/Options Menu

Фрагменты могут добавлять свои элементы в панель действий или меню активности. Сначала вы должны вызвать метод Fragment.setHasOptionsMenu() в методе фрагмента onCreate(). Затем нужно задать настройки для методов фрагмента onCreateOptionsMenu() и onOptionsItemSelected(), а также при необходимости для методов onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Работа методов фрагмента ничем не отличается от аналогичных методов для активности.

В активности, которая содержит фрагмент, данные методы автоматически сработают.

Если активность содержит собственные элементы панели действий или меню, то следует позаботиться, чтобы они не мешали вызовам методам фрагментов.

Код для активности:


public class MyActivity extends Activity {
    // ...

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.activity_options, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_activity_info:
            // Handle activity menu item
            return true;
        default:
            // Handle fragment menu items
            return super.onOptionsItemSelected(item);
        }
    }

    // ...
}

Код для фрагмента:


public class MyFragment extends Fragment {
    // ...

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(R.menu.myfragment_options, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.menu_first_info:
            // Handle fragment menu item
            return true;
        default:
            // Not one of ours. Perform default menu processing
            return super.onOptionsItemSelected(item);
        }
    }

    // ...
}

Связь между фрагментом и активностью

Экземпляр фрагмента связан с активностью. Активность может вызывать методы фрагмента через ссылку на объект фрагмента. Доступ к фрагменту можно получить через методы findFragmentById() или findFragmentByTag().

Фрагмент в свою очередь может получить доступ к своей активности через метод Fragment.getActivity().


View listView = getActivity().findViewById(R.id.list);

Дополнительное чтение

Реклама

В этом уроке:

— рассмотрим взаимодействие между Activity и ее фрагментами

После размещения фрагмента, хотелось бы начать с ним взаимодействовать. Т.е. размещать View-компоненты и работать с ними, обращаться к фрагментам из Activity и наоборот. Попробуем это реализовать.

Для чистоты эксперимента будем работать с двумя фрагментами: статическим и динамическим.

Создадим проект:

Project name: P1061_FragmentActivity
Build Target: Android 4.1
Application name: FragmentActivity
Package name: ru.startandroid.develop.p1061fragmentactivity
Create Activity: MainActivity

В strings.xml добавим строки:

<string name="frag1_text">Fragment 1</string>
<string name="frag2_text">Fragment 2</string>
<string name="log">Log</string>
<string name="find">Find</string>

Создаем layout и классы для двух фрагментов.

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="#77ff0000"
	android:orientation="vertical">
	<TextView
		android:id="@+id/textView"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/frag1_text">
	</TextView>
	<Button
		android:id="@+id/button"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/log">
	</Button>
</LinearLayout>

fragment2.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="#7700ff00"
	android:orientation="vertical">
	<TextView
		android:id="@+id/textView"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/frag2_text">
	</TextView>
	<Button
		android:id="@+id/button"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="@string/log">
	</Button>
</LinearLayout>

Fragment1.java:

package ru.startandroid.develop.p1061fragmentactivity;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment1 extends Fragment {

  final String LOG_TAG = "myLogs";

  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");
      }
    });
    
    return v;
  }
}

У фрагмента нет привычного для нас метода findViewById для поиска компонентов с экрана. Поэтому вызываем этот метод для View, которое будет содержимым фрагмента. В методе onCreateView мы создаем View и сразу же находим в нем кнопку и ставим ей обработчик. Затем отдаем View системе.

Fragment2.java:

package ru.startandroid.develop.p1061fragmentactivity;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment2 extends Fragment {

  final String LOG_TAG = "myLogs";

  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.

Настраиваем основное Activity.

main.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">
	<Button
		android:id="@+id/btnFind"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:onClick="onClick"
		android:text="@string/find">
	</Button>
	<fragment
		android:id="@+id/fragment1"
		android:name="ru.startandroid.develop.p1061fragmentactivity.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>

Кнопка, компонент fragment, в который помещен Fragment1, и контейнер FrameLayout, в который потом поместим Fragment2.

Обратите внимание на атрибут tools:layout. В нем указан layout-файл, и мы можем на этапе разработки видеть, как будет выглядеть статический фрагмент, когда приложение будет запущено.

Для этого надо нажать правой кнопкой на компоненте fragment, и через пункт Fragment Layout указать нужный layout.

MainActivity.java:

package ru.startandroid.develop.p1061fragmentactivity;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Fragment frag2 = new Fragment2();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.fragment2, frag2);
        ft.commit();
    }
}

Здесь мы просто добавляем Fragment2 в контейнер.

Все сохраняем, запускаем приложение.

Жмем кнопку Log в первом фрагменте и смотрим лог:

Button click in Fragment1

Жмем Log во втором фрагменте:

Button click in Fragment2

Все ок. Компоненты в фрагментах нашлись и обработчики среагировали на нажатия.

Атрибут onClick, который мы привыкли использовать для кнопки, здесь не прокатит. Указанный в этом атрибуте метод, будет вызван в Activity, а не в фрагменте.

Доступ к фрагменту из Activity

Разберемся, как получить доступ к фрагменту из Activity. Для этого у FragmentManager есть метод findFragmentById, который на вход принимает id компонента fragment (если фрагмент статический) или id контейнера (если динамический).

У нас в main.xml есть кнопка btnFind, вызывающая метод onClick при нажатии. Дорисуем в MainActivity.java метод onClick:

  public void onClick(View v) {
    Fragment frag1 = getFragmentManager().findFragmentById(R.id.fragment1);
    ((TextView) frag1.getView().findViewById(R.id.textView))
        .setText("Access to Fragment 1 from Activity");

    Fragment frag2 = getFragmentManager().findFragmentById(R.id.fragment2);
    ((TextView) frag2.getView().findViewById(R.id.textView))
        .setText("Access to Fragment 2 from Activity");
  }

Используем метод findFragmentById. В первом случае на вход передаем id компонента fragment, т.к. Fragment1 у нас размещен именно так. При поиске Fragment2 указываем id контейнера, в который этот фрагмент был помещен. В результате метод findFragmentById возвращает нам объект Fragment.

Далее мы получаем доступ к его View с помощью метода getView, находим в нем TextView и меняем текст.

Все сохраняем, запускаем. Жмем кнопку Find

Тексты в фрагментах обновились. Тем самым из Activity мы достучались до фрагментов и их компонентов.

На всякий случай проговорю одну вещь из разряда «Спасибо кэп!». Если посмотреть на код MainActivity, то можно заметить, что работая с frag2 в методе onCreate и с frag2 в методе onClick мы работаем с текущим фрагментом Fragment2. Это так и есть. Оба frag2 в итоге будут ссылаться на один объект. Так что, если вы динамически добавили фрагмент, то у вас уже есть ссылка на него, и искать его через findFragmentById вам уже не надо.

Доступ к Activity из фрагмента 

Теперь попробуем из фрагмента поработать с Activity. Для этого фрагмент имеет метод getActivity.

Давайте перепишем обработчик кнопки в первом фрагменте. Будем менять текст кнопки btnFind.

Fragment1.java:

package ru.startandroid.develop.p1061fragmentactivity;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment1 extends Fragment {

  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) {
        ((Button)getActivity().findViewById(R.id.btnFind)).setText("Access from Fragment1");
      }
    });
    
    return v;
  }
}

Получаем Activity методом getActivity, ищем в нем кнопку и меняем текст.

Сохраняем, запускаем. Жмем кнопку в первом фрагменте:

Работает. Из фрагмента мы поменяли компонент Activity.

Обработка в Activity события из фрагмента 

Рассмотрим механизм, который описан в хелпе: фрагмент генерирует некое событие и ставит Activity обработчиком.

Например, в Activity есть два фрагмента. Первый – список заголовков статей. Второй – отображает содержимое статьи, выбранной в первом. Мы нажимаем на заголовок статьи в первом фрагменте и получаем содержимое во втором. В этом случае, цель первого фрагмента – передать в Activity информацию о том, что выбран заголовок. А Activity дальше уже сама решает, что делать с этой информацией. Если, например, приложение запущено на планшете в горизонтальной ориентации, то можно отобразить содержимое статьи во втором фрагменте. Если же приложение запущено на смартфоне, то экран маловат для двух фрагментов и надо запускать отдельное Activity со вторым фрагментом, чтобы отобразить статью.

Фишка тут в том, что первому фрагменту неинтересны все эти терзания Activity. Фрагмент – обособленный модуль. Его дело — проинформировать, что выбрана статья такая-то. Ему не надо искать второй фрагмент и работать с ним – это дело Activity.

Тут немного отвлекусь на небольшое лирическое отступление. Модульность, вообще, — очень важная и полезная штука. И ее надо использовать для универсальности, удобности и легкости в понимании работы своих приложений. Но уникальных рецептов, как правильно все организовать, конечно, нет. Каждый делает по-своему. Именно по этим причинам я в своих уроках даю чисто технические вещи про отдельные компоненты и не рассказываю, как организовывать и писать целое приложение. Иначе, форум бы уже ломился от сообщений, что я все делаю не так и надо по-другому, и каждый бы излагал свое видение. И была бы куча споров, где одна сторона говорит, что крокодил зеленый, а другая сторона говорит, что он нифига не зеленый, а длинный ))

Вернемся к уроку. Фрагмент должен сообщить в Activity, что выбрана статья. Для этого он будет вызывать некий метод в Activity. И как нам сообщает хелп, лучший способ тут – это использовать интерфейс, который мы опишем в фрагменте и который затем будет реализован в Activity. Схема известная и распространенная. Давайте реализуем. В нашем приложении никаких статей нет, поэтому будем просто передавать произвольную строку из второго фрагмента в Activity. А Activity уже будет отображать эту строку в первом фрагменте.

Перепишем Fragment2.java:

package ru.startandroid.develop.p1061fragmentactivity;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;

public class Fragment2 extends Fragment {
  
  public interface onSomeEventListener {
    public void someEvent(String s);
  }
  
  onSomeEventListener someEventListener;
  
  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
        try {
          someEventListener = (onSomeEventListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement onSomeEventListener");
        }
  }

  final String LOG_TAG = "myLogs";

  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) {
        someEventListener.someEvent("Test text to Fragment1");
      }
    });
    
    return v;
  }
}

Описываем интерфейс onSomeEventListener. В нем метод someEvent, который на вход получает строку. Этот интерфейс будет реализовывать Activity.

В методе onAttach мы на вход получаем Activity, к которому присоединен фрагмент. Мы пытаемся привести это Activity к типу интерфейса onSomeEventListener, чтобы можно было вызывать метод someEvent и передать туда строку.  Теперь someEventListener ссылается на Activity.

Далее, в onCreateView, в обработчике кнопки мы вызываем метод someEvent и передаем туда текст. Этот метод будет отработан в Activity.

Теперь меняем Activity.

MainActivity.java:

package ru.startandroid.develop.p1061fragmentactivity;

import ru.startandroid.develop.p1061fragmentactivity.Fragment2.onSomeEventListener;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity implements onSomeEventListener{

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Fragment frag2 = new Fragment2();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.add(R.id.fragment2, frag2);
        ft.commit();
    }

  @Override
  public void someEvent(String s) {
      Fragment frag1 = getFragmentManager().findFragmentById(R.id.fragment1);
      ((TextView)frag1.getView().findViewById(R.id.textView)).setText("Text from Fragment 2:" + s);
  }
}

Дописываем интерфейс onSomeEventListener к описанию класса.

onCreate без изменений.

Реализуем метод someEvent. Просто ищем первый фрагмент и вставляем туда текст.

Все сохраняем и запускаем. Жмем кнопку во втором фрагменте:

Второй фрагмент передал через интерфейс строку в Activity, а оно нашло первый фрагмент и отобразило там эту строку.

На следующем уроке:

— размещаем элементы в ActionBar


Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

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


Есть два стандартных метода, которые вы можете вызвать на активити, чтобы найти в ней фрагмент. Это findFragmentByTag(String) и findFragmentById(Int). Данные методы позволяют найти фрагмент из активити.

Первый метод находит фрагмент по тегу, который был использован при добавлении фрагмента методами add() или replace(). Если данный метод не находит фрагмента, который был добавлен с таким тегом, то он возвращает null.

Метод же findFragmentById() принимает в качестве аргумента id. Id можно задать контейнером фрагмента (аргумент containerViewId при вызове add() или replace()), либо же в XML файле как атрибут тега <fragment>

Начал работать с фрагментами, создал NavigationBar, при выборе определенных пунктов подгружаются фрагменты, при нажатии кнопок на фрагментах кидает на новую активность. На эмуляторах, и вообще на всех физических устройствах знакомых и всех которые я нашел все работает, но на моем Meizu M2 mini при возвращении с активности обратно в меню с фрагментами, ранее подгруженный фрагмент не удаляется.
В приложении я определял наличие фрагментов через теги.

Вопрос как можно проверить есть ли какие-либо фрагменты на активности ? И с чем вообще может быть связано такое поведение ?

задан 10 июл 2016 в 6:06

Amir Shabanov's user avatar

Проверить можно так:

Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);

ответ дан 10 июл 2016 в 6:51

Kota1921's user avatar

Kota1921Kota1921

2,74613 серебряных знаков26 бронзовых знаков

2

Возвращает количество записей стека в настоящее время:

getFragmentManager().getBackStackEntryCount();

ответ дан 10 июл 2016 в 7:07

Shwarz Andrei's user avatar

Shwarz AndreiShwarz Andrei

12k1 золотой знак19 серебряных знаков40 бронзовых знаков

In my main.xml I have

  <FrameLayout
        android:id="@+id/frameTitle"
        android:padding="5dp"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:background="@drawable/title_bg">
            <fragment
              android:name="com.fragment.TitleFragment"
              android:id="@+id/fragmentTag"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content" />

  </FrameLayout>

And I’m setting fragment object like this

FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment newFragment = new FragmentType1();
fragmentTransaction.replace(R.id.frameTitle, casinodetailFragment, "fragmentTag");

// fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();

It is setting different types of Fragment objects (FragmentType2,FragmentType3,...) at different time. Now at some point of time I need to identify which object is currently there.

In short I need to do something like this:

Fragment currentFragment = //what is the way to get current fragment object in FrameLayout R.id.frameTitle

I tried the following

TitleFragment titleFragmentById = (TitleFragment) fragmentManager.findFragmentById(R.id.frameTitle);

and

    TitleFragment titleFragmentByTag = (TitleFragment) fragmentManager.findFragmentByTag("fragmentTag");

But both the objects (titleFragmentById and titleFragmentByTag ) are null
Did I miss something?
I’m using Compatibility Package, r3 and developing for API level 7.

findFragmentById() and findFragmentByTag() will work if we have set fragment using fragmentTransaction.replace or fragmentTransaction.add, but will return null if we have set the object at xml (like what I have done in my main.xml). I think I’m missing something in my XML files.

Понравилась статья? Поделить с друзьями:

Не пропустите также:

  • Как найти имена существительные прилагательное глагол
  • Как найти массу если известен молярный объем
  • Как найти длину проводника зная магнитную индукцию
  • Экспонента в степени как найти степень
  • Как найти девочкам мужей

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии