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

108

В Android 4.2 библиотека поддержки получила поддержку вложенных фрагментов, см. Здесь . Я поигрался с ним и обнаружил интересное поведение / ошибку в отношении заднего стека и getChildFragmentManager () . При использовании getChildFragmentManager () и addToBackStack (String name) при нажатии кнопки возврата система не спускается по стеку назад к предыдущему фрагменту. С другой стороны, при использовании getFragmentManager () и addToBackStack (String name) при нажатии кнопки возврата система возвращается к предыдущему фрагменту.

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

Это правильное поведение? Это ошибка? Есть ли способ решить эту проблему?

пример кода с getChildFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

пример кода с getFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}
AZ13
источник
хммм, это очень интересно. Я тоже не ожидал такого поведения. Если это так, то кажется, что мы могли переопределить OnBackPressed и в этом методе получить фрагмент с дочерними фрагментами, а затем удержать ChildFragmentManager для выполнения транзакции pop.
Marco RS
@Marco, Точно, но все же обходной путь. Почему API не работает должным образом?
Егор

Ответы:

67

Это решение может быть лучшей версией ответа @Sean:

@Override
public void onBackPressed() {
    // if there is a fragment and the back stack of this fragment is not empty,
    // then emulate 'onBackPressed' behaviour, because in default, it is not working
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

Опять же, я подготовил это решение на основе ответа @Sean выше.

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

Примечание. Поскольку getFragmentsметод теперь является частным, это решение не сработает. Вы можете проверить комментарии по ссылке, которая предлагает решение этой ситуации.

Исмаиларылик
источник
2
林奕 忠 ответ следует использовать вместо этого - он правильно обрабатывает несколько вложенных фрагментов
Хамено
Это действительно рабочий код? Даже getFragments не общедоступный метод? stackoverflow.com/a/42572412/4729203
wonsuc
Когда-то он работал, но может не работать сейчас, если getFragments больше не является общедоступным.
ismailarilik
1
getFragmentsтеперь публично.
grrigore
это не работает правильно, если у вас есть FragA -> Frag B -> Frag C внутри пейджера ...
Жар
57

Похоже на ошибку. Взгляните на: http://code.google.com/p/android/issues/detail?id=40323

Для обходного пути я успешно использовал (как было предложено в комментариях):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}
Шон
источник
12
К сожалению, это не подходящее решение и просто обходной путь в простейшем случае. Просто представьте, что у вас есть фрагмент, который содержит другой фрагмент, который содержит другой фрагмент и так далее. Затем вам нужно распространить вызов до последнего фрагмента, который содержит еще один или несколько фрагментов: /
AZ13
@MuhammadBabar Нет, не поймешь. Если это ноль, вторая часть оператора не будет оцениваться, потому что она уже определена как ложная. операнд - &, а не | .
Шон
25

Настоящий ответ на этот вопрос находится в функции транзакции фрагмента, называемой setPrimaryNavigationFragment.

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

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

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

Это дает вам такое же поведение, как если бы вы возвращались с обычного фрагмента B обратно на фрагмент A, за исключением того, что теперь это происходит и с дочерними фрагментами!

Марио Бушедид
источник
3
Отличная находка! Это решает проблему, упомянутую OP, гораздо более чистым и менее хакерским способом.
Jona
Это сбой, если часть библиотеки навигации не является HavHostFragment
Диди
22

Это решение может быть лучшей версией ответа @ismailarilik:

Версия вложенного фрагмента

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}
林奕 忠
источник
он не работает, когда есть несколько уровней вложенных фрагментов
splinter123
Облако вы мне свою архитектуру вложенных фрагментов? В моем приложении это работает. Может я что-то упускаю.
林奕 忠 01
9
Порядок извлечения backstack не такой, как я ожидал в этом примере. Я считаю, что вы должны сначала найти наиболее глубоко вложенный видимый фрагмент в дереве и попытаться вставить в него заднюю стопку, а затем постепенно продвигаться вверх по дереву в поисках других видимых фрагментов, где вы можете вставить заднюю стопку, пока не получите один. Быстрое исправление, которое обрабатывает простейшие случаи, - это изменить функцию onBackPressed (FragmentManager fm) и переключить блок, который выталкивает backstack, на блок, который перечисляет фрагменты. Таким образом, сначала произойдет выталкивание бэкстэка в более глубоко вложенные дочерние фрагменты
Тео
1
Кроме того, когда у вас много вложенных фрагментов, каждый со своими дочерними фрагментами, которые могут покрывать часть экрана, идея иметь отдельный backstack для каждого диспетчера дочерних фрагментов теряет смысл. Backstack должен быть унифицирован для всех фрагментов в действии и отслеживать изменения фрагментов в том порядке, в котором они происходят, независимо от уровня вложенности фрагмента.
Тео
Можно ли использовать это решение без стандарта, SupportFragmentManagerа вместо него FragmentManager?
Питер Чаппи
13

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

Добавьте это в свою Activity, которая расширяет AppCompatActivity.

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

Добавьте это в свой BaseFragment или в класс, от которого вы можете унаследовать все свои фрагменты.

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}
Саймон
источник
это отличный ответ, работающий на уровне API 23/24 и с поддержкой lib 24.1.1
Ринав
Я использую это в течение некоторого времени, это отличное решение, но недавно появилось предупреждение компиляции о прямом доступе к getFragments () из внешней группы библиотек. Вы уже обновили обходной путь?
Саймон Хакетт
что-то исправлено или в 2020 году у нас все еще есть проблема, и нужно реализовать ваше решение ?? !!
Жар
3

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

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

Вызов метода:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}
AndroidDev
источник
Это работает для всех вложенных фрагментов, если он правильно зарегистрирован !! +100
Pratik Saluja 09
2

Причина в том, что ваше действие происходит от FragmentActivity, который обрабатывает нажатие клавиши BACK (см. Строку 173 FragmentActivity .

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

  • определение интерфейса OnBackKeyPressedListener с помощью одного метода void onBackKeyPressed()
  • реализовал этот интерфейс в «верхних» фрагментах, которые показывает ViewPager
  • переопределение onKeyDown и обнаружение нажатия BACK и вызов onBackKeyPressed в текущем активном фрагменте в пейджере просмотра.

Также обратите внимание, что я использую getChildFragmentManager()в фрагментах для правильного размещения фрагментов. Вы можете увидеть обсуждение и объяснение в этом посте разработчиков Android .

миха
источник
2

если вы используете фрагмент во фрагменте, используйте getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

если вы используете дочерний фрагмент и замените его, используйте getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

если оба не работают, попробуйте это getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
Четан
источник
1

Я смог обработать фрагмент обратного стека, добавив к родительскому фрагменту этот метод в методе onCreate View () и передав корневое представление.

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

Метод isEnableFragmentBackStack () - это логический флаг, позволяющий узнать, когда я нахожусь на основном или следующем фрагменте.

Убедитесь, что при фиксации фрагмента, который должен иметь стек, вы должны добавить метод addToBackstack.

ЭкКоЗ
источник
1

Это решение может быть лучше, поскольку оно проверяет все уровни вложенных фрагментов:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * If there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

в своей деятельности onBackPressedвы можете просто назвать это так:

FragmentManager fm = getSupportFragmentManager();
                // if there is a fragment and the back stack of this fragment is not empty,
                // then emulate 'onBackPressed' behaviour, because in default, it is not working
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }
ahmed_khan_89
источник
1

Спасибо всем за помощь, у меня работает эта (измененная версия):

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

ПРИМЕЧАНИЕ. Возможно, вы также захотите установить цвет фона этих вложенных фрагментов в соответствии с цветом фона окна темы приложения, поскольку по умолчанию они прозрачны. В некоторой степени выходит за рамки этого вопроса, но это достигается путем разрешения атрибута android.R.attr.windowBackground и установки фона представления фрагмента на этот идентификатор ресурса.

Стивен Л
источник
1

Более 5 лет и этот вопрос по-прежнему актуален. Если вы не хотите использовать fragmentManager.getFragments () из-за его ограничений. Расширьте и используйте следующие классы:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

NestedFragment.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

Объяснение:

Каждое действие и фрагмент, расширенный из вышеупомянутых классов, управляет своим собственным обратным стеком для каждого из своих дочерних элементов, дочерних элементов детей и т. Д. Backstack - это просто запись тегов / идентификаторов «активного фрагмента». Поэтому предостережение - всегда предоставлять тег и / или идентификатор для вашего фрагмента.

При добавлении в backstack в childFragmentManager вам также потребуется вызвать addToParentBackStack (). Это гарантирует, что тег / идентификатор фрагмента будет добавлен в родительский фрагмент / действие для последующих всплывающих окон.

Пример:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();
охотник
источник
1

Я реализовал это правильно, если до сих пор никто не нашел ответа

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

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}
Сунил Чаудхари
источник
0

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

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

Вот как я добавляю дочерний фрагмент в текущий открытый фрагмент из самого себя.

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

Это xml-макет родительского фрагмента и добавленного фрагмента

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>
Михал Жраднк Ноно3551
источник
0

После наблюдения за некоторыми решениями, представленными здесь, я обнаружил, что для обеспечения гибкости и контроля для родительского фрагмента относительно того, когда он выталкивает стек или когда обратное действие должно игнорироваться им, я предпочитаю использовать такую ​​реализацию:

Определение интерфейса «ParentFragment»:

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

Переопределение onBackPressed в родительском действии (или в BaseActivity):

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

А затем разрешите родительскому фрагменту обрабатывать его так, как он хочет, например:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

Это позволяет не думать, что есть какой-то законный дочерний фрагмент, который нужно удалить, например, при использовании пейджера просмотра (и количество записей в обратном стеке> 0).

Диди
источник
-1

Если у вас есть DialogFragmentвложенные фрагменты, «обходной путь» будет немного другим. Вместо того, чтобы устанавливать onKeyListenerдля rootView, вам нужно сделать это с помощью Dialog. Также вы будете настраивать a, DialogInterface.OnKeyListenerа не Viewтот. Конечно, помните addToBackStack!

Кстати, наличие 1 фрагмента на заднем стеке для делегирования обратного вызова активности - это мой личный вариант использования. В типичных сценариях счетчик может быть равен 0.

Вот что вам нужно сделать в onCreateDialog

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }
Сачин Ахуджа
источник
Не совсем понятно или неполно. DialogFragment или его суперкласс Fragment не имеют setOnKeyListener.
Ixx
@Ixx Вы должны включить слушателя Dialog(не DialogFragment), и слушатель, который вы используете, есть DialogInterface.OnKeyListener- код работает и завершен (и используется). Почему бы вам не рассказать о своей ситуации, и, возможно, я смогу помочь.
Sachin Ahuja
-1

для ChildFragments это работает ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
user3854496
источник