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


108

З Android 4.2 бібліотека підтримки отримала підтримку вкладених фрагментів, дивіться тут . Я пограв з цим і виявив цікаву поведінку / помилку щодо back stack та getChildFragmentManager () . Використовуючи getChildFragmentManager () та addToBackStack (ім'я рядка), натисканням кнопки "назад" система не запускає задній стек до попереднього фрагмента. З іншого боку, використовуючи getFragmentManager () та addToBackStack (ім'я рядка), натисканням кнопки "назад" система повертається до попереднього фрагмента.

Для мене така поведінка несподівана. Натискаючи кнопку "назад" на своєму пристрої, я очікую, що останній доданий фрагмент до задньої стеки буде вискакуватися, навіть якщо фрагмент буде доданий до заднього стеку в диспетчері фрагментів дітей.

Чи правильна така поведінка? Це поведінка помилка? Чи існує вирішення цього питання?

зразок коду з 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);
    }
}

}

Хммм це дуже цікаво. Я навіть не очікував, що такої поведінки. Якщо це так, то, здається, що ми могли б переотримати OnBackPress, і в цьому методі утримати фрагмент з дочірніми фрагментами, а потім отримати власну програму ChildFragmentManager для здійснення поп-транзакції.
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 всередині Pager ...
Zhar

57

Схоже на помилку. Погляньте на сторінку: http://code.google.com/p/android/isissue/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);

Ви повинні встановити цю функцію на початковому батьківському фрагменті, коли активність додає її. У мене в моїй діяльності є функція substituFragment, яка виглядає приблизно так:

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
Чудова знахідка! Це вирішує питання, про яке йдеться в ОП, значно чіткіше і менш хакі.
Йона

Це збої, якщо тільки частина навігаційної бібліотеки як 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

Хмара, ти передаєш мені свою архітектуру вкладених фрагментів? У моєму додатку це працює. Можливо, я щось сумую.
林奕 忠

9
Порядок вискокнення рюкзака не такий, як я очікував у цьому прикладі. Я вважаю, що вам слід спочатку знайти найбільш глибоко вкладений видимий фрагмент на дереві та спробувати вискочити туди рюкзак, а потім поступово рухатись вгору по дереву, шукаючи інші видимі фрагменти, де ви могли б вискочити туди, поки не отримаєте. Швидке виправлення, що обробляє найпростіші випадки, - це змінити функцію onBackPress (FragmentManager fm) та переключити блок, що спливає задню ранку, на той, який перераховує фрагменти. Таким чином, спочатку відбудеться перескок на рюкзак у більш глибоко вкладені дитячі фрагменти
Тео

1
Крім того, коли у вас є багато вкладених фрагментів, кожен зі своїми дочірніми фрагментами, які можуть охоплювати частину екрана, ідея мати окремий задній стік для кожного дочірнього менеджера фрагментів перестає мати сенс. Рюкзак повинен бути уніфікований за всіма фрагментами в діяльності та відстежувати зміни фрагментів у тому порядку, в якому вони відбуваються, незалежно від рівня вкладеності фрагмента.
Тео

Чи можливо використовувати це рішення без, SupportFragmentManagerа замість цього просто Стандарт FragmentManager?
Пітер Чаппі

13

З цією відповіддю він оброблятиме рекурсивну перевірку зворотної сторони та надасть кожному фрагменту можливість змінити поведінку за замовчуванням. Це означає, що ви можете мати фрагмент, на якому розміщено ViewPager, зробити щось особливе, наприклад прокручування до сторінки, яка є резервною стеком, або прокручування до домашньої сторінки, а потім на наступному натисканні виходу назад.

Додайте це до своєї діяльності, яка розширює 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
Rinav

Використовую це вже деякий час, це чудове рішення - але останнім часом скласти попередження про прямий доступ до getFragments () з групи, що знаходиться поза бібліотекою. Ви вже оновлювали обхідні роботи?
Simon Huckett

це щось виправлене, або у 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();
}

Це працює для всіх вкладених фрагментів, якщо його зареєстровано правильно !! +100
Пратік Салуя

2

Причина полягає в тому, що ваша активність походить від FragmentActivity, який обробляє натискання клавіші BACK (див. Рядок 173 FragmentActivity .

У нашій програмі я використовую ViewPager (з фрагментами), і кожен фрагмент може мати вкладені фрагменти. Я вирішив це:

  • визначення інтерфейсу OnBackKeyPressListener одним методом void onBackKeyPressed()
  • реалізував цей інтерфейс у "верхніх" фрагментах, які показує ViewPager
  • переосмислення onKeyDown та виявлення натискання BACK та виклик onBackKeyPress у поточному активному фрагменті в пейджері перегляду.

Також зауважте, що я використовую getChildFragmentManager()фрагменти, щоб правильно вкласти фрагменти. Ви можете побачити обговорення та пояснення в цьому пості для андроїд-розробників .


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

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 у 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>

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

}

Перезавантаження "onBackPress" у батьківській діяльності (або в 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. Крім того, ви будете встановлювати, DialogInterface.OnKeyListenerа не Viewодин. Звичайно, пам’ятайте addToBackStack!

Btw, маючи 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 або його фрагмент суперкласу не мають setOnKeyListener.
Ixx

@Ixx Ви повинні ввімкнути слухача Dialog(не DialogFragment), а слухач, яким ви користуєтесь, DialogInterface.OnKeyListener- код працює та завершений (і використовується). Чому б ви не надали свою ситуацію, і, можливо, я можу допомогти.
Сахін Ахуджа

-1

для ChildFragments це працює ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.