Фрагменты внутри фрагментов

Мне интересно, если это на самом деле ошибка в Android API:

У меня есть такая настройка:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. Это меню, которое загружает фрагмент № 2 (экран поиска) в правой панели.
  2. Это экран поиска, который содержит фрагмент #3, который является списком результатов.
  3. Список результатов используется в нескольких местах (в том числе как функционирующий фрагмент высокого уровня сам по себе).

Эта функция прекрасно работает на телефоне (где 1, 2 и 3 ActivityFragmentс).

Тем не менее, когда я использовал этот код:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

куда R.id.leftPane а также R.id.rightPane являются <fragment>в горизонтальной линейной компоновке.

Насколько я понимаю, приведенный выше код удаляет фрагмент, который является резидентным, а затем заменяет его новым фрагментом. Замечательно... Очевидно, это не то, что происходит, потому что, когда этот код запускается во второй раз, вы получаете следующее исключение:

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

Это вызвано тем, что контейнер для FragmentNumber3 был продублирован и у него больше нет уникального идентификатора. Первоначальный фрагмент не был уничтожен (?) До добавления нового (на мой взгляд, это означает, что он не был заменен).

Может кто-нибудь сказать мне, если это возможно ( этот ответ предполагает, что это не так), или это ошибка?

6 ответов

Решение

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

Обновление: Вложенные фрагменты поддерживаются начиная с Android 4.2 (и библиотеки поддержки Android, редакция 11): http://developer.android.com/about/versions/android-4.2.html

ПРИМЕЧАНИЕ (согласно данным документам): " Примечание. Вы не можете раздувать макет на фрагмент, если этот макет содержит <fragment> , Вложенные фрагменты поддерживаются только при динамическом добавлении к фрагменту. "

Вложенные фрагменты поддерживаются в Android 4.2 и позже

Библиотека поддержки Android также теперь поддерживает вложенные фрагменты, так что вы можете реализовать проекты с вложенными фрагментами на Android 1.6 и выше.

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

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

Чтобы получить больше информации о вложенных фрагментах, пройдите эти уроки
Часть 1
Часть 2
Часть 3

и вот SO сообщение, в котором обсуждаются лучшие практики для вложенных фрагментов.

.. вы можете очистить свой вложенный фрагмент в родительском фрагменте destroyview метод:

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }

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

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

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

Я надеюсь, что это помогает вам.

Я столкнулся с той же проблемой, пару дней боролся с ней и должен сказать, что самый простой способ преодоления, который я нашел, - это использовать frag.hide() / фрагмент.show(), когда вкладка выбрана / не выбрана. ().

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

При повороте экрана все родительские и дочерние фрагменты корректно уничтожаются.

У этого подхода есть еще одно дополнительное преимущество - использование hide()/show() не приводит к тому, что представления фрагментов теряют свое состояние, поэтому нет необходимости восстанавливать предыдущую позицию прокрутки для ScrollView, например.

Проблема в том, что я не знаю, правильно ли не отрывать фрагменты, когда они не видны. Я думаю, что официальный пример TabListener разработан с мыслью, что фрагменты можно использовать повторно, и вы не должны загрязнять ими память, однако, я думаю, что если у вас всего несколько вкладок и вы знаете, что пользователи будут часто переключаться между ними, будет целесообразно оставить их привязанными к текущей деятельности.

Я хотел бы услышать комментарии от более опытных разработчиков.

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

transaction.add(R.id.placeholder, newFragment);

в

transaction.replace(R.id.placeholder, newFragment);

Если выше не поможет, попробуйте:

Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

Узнал здесь

Другие вопросы по тегам