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

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

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

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

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

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

    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> в горизонтальной линейной компоновке.

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

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

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

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

@Override
    public void onDestroyView() {

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

        transaction.remove(nestedFragment);

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

        super.onDestroyView();
    }

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

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

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

Ответ 6

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

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

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