ViewPager с вложенными фрагментами?

Моя проблема

Согласно документам Google:

Теперь вы можете вставлять фрагменты внутрь фрагментов. Это полезно в различных ситуациях, когда вы хотите поместить динамические и повторно используемые компоненты пользовательского интерфейса в компонент пользовательского интерфейса, который сам по себе является динамическим и повторно используемым. Например, если вы используете ViewPager для создания фрагментов пролистывания влево и вправо и занимают большую часть экрана, вы теперь можете вставлять фрагменты в каждую страницу фрагмента. . Чтобы вложить фрагмент, просто вызовите getChildFragmentManager() для фрагмента, в который вы хотите добавить фрагмент. Это возвращает FragmentManager, который вы можете использовать, как обычно, из активности верхнего уровня для создания транзакций фрагментов. Например, вот код, который добавляет фрагмент из существующего класса Fragment:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

Поэтому я создал свой собственный PageFragment. А мой PageFragmentAdapterсостоит из 3-х страниц PageFragment. У меня также есть шесть фрагментов: Fragment1, Fragment2, Fragment3, FragmentA, FragmentB, FragmentC. В своем MainActivity я инициализирую свои шесть фрагментов, свой ViewPager и его PageFragmentAdapter (которые инициализируют три PageFragment для себя).

Затем я использую этот метод, чтобы прикрепить свои Fragment1, Fragment2, Fragment3 к каждой странице (к каждому PageFragment) PageFragmentAdapter:

PageFragmentAdapter tPageFragmentAdapter = new PageFragmentAdapter(getSupportFragmentManager());
tPageFragmentAdapter.setFirstPage(tFragment1);
tPageFragmentAdapter.setSecondPage(tFragment2);
tPageFragmentAdapter.setThirdPage(tFragment3);

Я могу получить (с помощью методов в PageFragmentAdapter) фрагменты и сделать «вложенный фрагмент» в свой PageFragment (конечно, в onCreate()), например:

getChildFragmentManager().beginTransaction()
.add(R.id.framelayout_history, mFragment)
.addToBackStack(null)
.commit();

Примечание. Я использую PageFragment.setNestedFragment(), чтобы установить в нем mFragment.

И это прекрасно работает! Мне поможет (но этот рассказ не об этом) простая замена фрагментов во ViewPager типа:

tPageFragmentAdapter.replaceFirstPage(tFragmentA);

Но у меня есть одна огромная проблема. Снова документы Google:

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

Как видите, PageFragmentAdapter может уничтожить каждый мой PageFragment и, конечно же, его вложенный фрагмент. И когда он будет воссоздан, он будет иметь значение NULL вместо mFragment. Я пытаюсь сохранить его через getChildFragmentManager().putFragment(), а затем получить через getChildFragmentManager().getFragment(). Но это не сработало.

Вопрос

Итак, мой вопрос: Как сохранить мой вложенный фрагмент в моем родительском фрагменте? Возможно ли это?

Я был бы очень признателен за вашу помощь. Алекс. P.S. Очень извиняюсь за мой английский :)

РЕДАКТИРОВАТЬ

Спасибо @AndroidBegin.com за ответ!

Похоже, это ошибка недавно добавленной поддержки вложенных фрагментов. По сути, дочерний FragmentManager завершается с нарушенным внутренним состоянием, когда он отсоединяется от активности. Краткосрочное обходное решение, которое исправило это для меня, состоит в том, чтобы добавить следующее в onDetach() каждого фрагмента, который вы вызываете getChildFragmentManager():

@Override
public void onDetach() {
    super.onDetach();

    try {
        Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
        childFragmentManager.setAccessible(true);
        childFragmentManager.set(this, null);

    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

person AlexMomotov    schedule 24.09.2013    source источник
comment
Помоги мне! Поделитесь своей идеей!   -  person AlexMomotov    schedule 24.09.2013


Ответы (2)


Попробуйте это на своем фрагменте.

public class Fragment2 extends SherlockFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.viewpager_main, container, false);
        // Locate the ViewPager in viewpager_main.xml
        ViewPager mViewPager = (ViewPager) view.findViewById(R.id.viewPager);
        // Set the ViewPagerAdapter into ViewPager
        mViewPager.setAdapter(new ViewPagerAdapter(getChildFragmentManager()));
        return view;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        try {
            Field childFragmentManager = Fragment.class
                    .getDeclaredField("mChildFragmentManager");
            childFragmentManager.setAccessible(true);
            childFragmentManager.set(this, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

Источник: https://www.swipetips.com/actionbarsherlock-side-menu-navigation-nested-viewpager-fragment-tabs-tutorial/

person AndroidBegin.com    schedule 24.09.2013
comment
OK. А как получить FragmentManager из поля? - person AlexMomotov; 27.09.2013
comment
Ознакомьтесь с этим вопросом для получения дополнительной информации stackoverflow.com/a/15656428/2397414 - person AndroidBegin.com; 28.09.2013
comment
Нет, не используйте это - person EpicPandaForce; 02.03.2020

Нет причин делать какие-либо из описанных хаков. Если вы используете ViewPager и PagerAdapter (не уверен, что они существовали на момент вопроса), вы можете использовать FragmentPagerAdapter для управления фрагментами вкладок, которые можно перелистывать.

class MyFragmentPagerAdapter(
  private val context: Context,
  fragmentManager: FragmentManager
) : FragmentPagerAdapter(fragmentManager) {
  override fun getCount() = 2

  override fun getItem(position: Int) = when(position) {
    0 -> FirstFragment()
    1 -> SecondFragment()
    else -> throw IllegalStateException("Unexpected position $position")
  }

  override fun getPageTitle(position: Int): CharSequence = when(position) {
    0 -> context.getString(R.string.first)
    1 -> context.getString(R.string.second)
    else -> throw IllegalStateException("Unexpected position $position")
  }
}

А также

class ParentFragment: Fragment() {
  override fun onCreateView(...) = ...

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewPager.adapter = MyFragmentPagerAdapter(requireContext(), childFragmentManager)
    tabLayout.setupWithViewPager(viewPager)
  }
}
person EpicPandaForce    schedule 24.11.2019
comment
Вам нужно позвонить clearOnPageChangeListeners на viewPager на onDestroy, если вы используете setupWithViewPager - person Daniel Gomez Rico; 24.04.2020