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

145

Мне интересно, если это на самом деле ошибка в 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 был продублирован и у него больше нет уникального идентификатора. Первоначальный фрагмент не был уничтожен (?) До добавления нового (на мой взгляд, это означает, что он не был заменен ).

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

Graeme
источник
1
возможный дубликат фрагмента внутри фрагмента
rds
6
@rds это древний вопрос, который бессмысленно отмечать как дубликат.
pietv8x

Ответы:

203

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

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

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

hackbod
источник
14
Не поддерживается, потому что это не было целью разработки для первоначальной реализации. Я слышал много запросов на эту функцию, так что, возможно, это будет сделано в какой-то момент, но, как обычно, есть много других вещей, которые конкурируют с ней по приоритету.
hackbod
4
Мне удалось это с помощью расширения FragmentActivity, FragmentManager и FragmentTransaction. Основная предпосылка - расширить DeferringFragmentActivity в моих действиях, предоставляя тот же API, поэтому никаких других изменений кода. Когда я вызываю getFragmentManager, я получаю экземпляр DeferringFragmentManager, а когда я вызываю beginTransaction, я получаю DeferredTransaction. Эта транзакция хранит POJO с вызванным методом и аргументами. Когда commit is call, мы сначала ищем все отложенные DeferredTransactions. Как только все транзакции были зафиксированы, мы запускаем реальную транзакцию и запускаем все хранимые методы с аргументами.
dskinner
11
Этот момент сейчас. Вложенные Fragments теперь являются частью Android API, да! developer.android.com/about/versions/… .
Алекс Локвуд
9
Вау, какой кошмар: если вы используете <фрагмент> для фрагмента, и этот фрагмент использует дочерние фрагменты, он не завершается с явной ошибкой («не удается добавить дочерние фрагменты во фрагменты макета») - это не работает таинственно с исключениями типа «фрагмент не создал вид». Там идет несколько часов отладки времени ...
Гленн Мейнард
6
@ MartínMarconcini конечно, но это совсем не очевидно, учитывая функциональность API. Если что-то не разрешено, это должно быть четко задокументировано, а не оставлять разработчика за волосы, потому что что-то работает не так, как вы ожидаете.
dcow
98

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

Раниз Ахмед
источник
Основной недостаток Nestedfragment - мы не можем вызвать optionmenu из childfragment :( если мы используем ABS!
LOG_TAG
Можете ли вы посмотреть в моем выпуске? Это очень похоже .. stackoverflow.com/questions/32240138/… . Для меня ребёнок framnet не становится раздутым из кода
Ник
33

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

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }
furykid
источник
4
Если вы выполните какое-либо тестирование жизненного цикла с помощью SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), вы увидите, что этот код вызывает ошибку, когда другое действие идет сверху с всегда включенным финишем (IllegalStateException: не удается выполнить это действие после onSaveInstanceState). Заключение кода выше в try / catch - не самое элегантное решение, но, похоже, все работает.
Тео
Это почти сработало. Позже я получил Stackoverflow при рисовании интерфейса. Определенно избегайте вложенных фрагментов ...
neteinstein
14

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

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

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

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

draksia
источник
Кто-нибудь может прокомментировать эффективность этого подхода? К сожалению, я могу использовать фрагменты только на один уровень глубины - с тем же успехом они могут вообще не использоваться. Добавление их программно в группы местозаполнителей будет работать без предостережений?
Рафаэль Нобре
По-прежнему, кажется, работает для меня, я поменяю их и из видоискателя также не проблема. Одно предостережение: я делаю это только на сотах, а не на совместимости с сэндвичем с мороженым.
Драксия
4

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

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

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

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

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

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

Ievgen
источник
0

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

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

Узнал здесь

Voy
источник