Фрагменты внутри фрагментов - PullRequest
145 голосов
/ 27 июля 2011

Мне интересно, действительно ли это ошибка в 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 ]

202 голосов
/ 27 июля 2011

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

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

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

98 голосов
/ 13 июня 2012

Вложенные фрагменты поддерживаются в 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-пост, в котором обсуждается лучшие практики для вложенных фрагментов .

33 голосов
/ 31 октября 2011

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

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }
14 голосов
/ 26 октября 2011

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

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

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

Надеюсь, это поможет вам.

4 голосов
/ 19 июля 2012

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

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

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

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

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

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

0 голосов
/ 19 июня 2016

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

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();

Изучено здесь

...