Android 4.2: zachowanie stosu wstecznego z zagnieżdżonymi fragmentami

108

W systemie Android 4.2 biblioteka obsługi otrzymała obsługę zagnieżdżonych fragmentów, patrz tutaj . Bawiłem się tym i znalazłem interesujące zachowanie / błąd dotyczący stosu wstecznego i metody getChildFragmentManager () . Używając getChildFragmentManager () i addToBackStack (nazwa ciągu), po naciśnięciu przycisku Wstecz system nie przesuwa stosu wstecznego do poprzedniego fragmentu. Z drugiej strony, korzystając z getFragmentManager () i addToBackStack (nazwa ciągu), po naciśnięciu przycisku wstecz system powraca do poprzedniego fragmentu.

Dla mnie takie zachowanie jest nieoczekiwane. Naciskając przycisk Wstecz na moim urządzeniu, spodziewam się, że ostatni dodany fragment do tylnego stosu zostanie usunięty, nawet jeśli fragment został dodany do tylnego stosu w menedżerze fragmentów dla dzieci.

Czy to zachowanie jest prawidłowe? Czy to zachowanie jest błędem? Czy istnieje obejście tego problemu?

przykładowy kod z 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);
    }
}

}

przykładowy kod z 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
źródło
hmmm to bardzo interesujące. Nie spodziewałbym się też takiego zachowania. Jeśli tak jest, to wydaje się, że mogliśmy przesłonić OnBackPressed i w tej metodzie uzyskać fragment z fragmentami potomnymi, a następnie uzyskać ChildFragmentManager, aby wykonać transakcję pop.
Marco RS
@Marco, Dokładnie, ale jest to obejście. Dlaczego API nie działa poprawnie?
Egor

Odpowiedzi:

67

To rozwiązanie może być lepszą wersją odpowiedzi @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();
}

Ponownie przygotowałem to rozwiązanie na podstawie odpowiedzi @Sean powyżej.

Jak powiedział @ AZ13, to rozwiązanie jest możliwe tylko w jednopoziomowych sytuacjach fragmentów potomnych. W przypadku fragmentów wielopoziomowych prace stają się trochę skomplikowane, więc polecam wypróbować to rozwiązanie tylko w przypadku wykonalnym, który powiedziałem. =)

Uwaga: ponieważ getFragmentsmetoda jest teraz metodą prywatną, to rozwiązanie nie będzie działać. W komentarzach możesz znaleźć link, który sugeruje rozwiązanie tej sytuacji.

ismailarilik
źródło
2
林奕 忠 należy użyć odpowiedzi zamiast tej - poprawnie obsługuje wiele zagnieżdżonych fragmentów
Hameno
Czy to naprawdę działa kod? Nawet getFragments nie jest metodą publiczną? stackoverflow.com/a/42572412/4729203
wonsuc
Kiedyś działało, ale może nie działać teraz, jeśli getFragments nie jest już publiczne.
ismailarilik
1
getFragmentsjest teraz publiczny.
grrigore
to nie działa poprawnie, jeśli masz FragA -> Frag B -> Frag C wewnątrz pagera ...
Zhar
57

Wygląda na błąd. Spójrz na: http://code.google.com/p/android/issues/detail?id=40323

Aby obejść problem, z którego pomyślnie skorzystałem (zgodnie z sugestią w komentarzach):

    @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();          
    }
}
Sean
źródło
12
Niestety nie jest to właściwe rozwiązanie i tylko obejście dla najprostszego przypadku. Po prostu załóżmy, że masz fragment, który zawiera inny fragment, który zawiera inny fragment i tak dalej. Następnie musisz propagować wywołanie do ostatniego fragmentu, który zawiera kolejny jeden lub więcej fragmentów: /
AZ13
@MuhammadBabar Nie, nie będziesz. Jeśli jest to null, druga część instrukcji nie zostanie oceniona, ponieważ została już określona jako fałszywa. operand to & a nie | .
Sean
25

Prawdziwa odpowiedź na to pytanie znajduje się w funkcji Fragment Transaction o nazwie 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);

Musisz ustawić tę funkcję na początkowym fragmencie nadrzędnym, gdy aktywność go dodaje. W mojej aktywności mam funkcję replaceFragment, która wygląda następująco:

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

Daje to takie samo zachowanie, jak w przypadku klikania z powrotem ze zwykłego fragmentu B z powrotem do fragmentu A, z wyjątkiem tego, że teraz dotyczy to również fragmentów potomnych!

Mario Bouchedid
źródło
3
Świetne znalezisko! To rozwiązuje problem, o którym wspomina PO, w znacznie bardziej przejrzysty i mniej hacki sposób.
Jona
To się zawiesza, chyba że jest częścią biblioteki nawigacji jako HavHostFragment
Didi
22

Takim rozwiązaniem może być lepsza wersja odpowiedzi @ismailarilik:

Wersja zagnieżdżonego fragmentu

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();
}
林奕 忠
źródło
wydaje się, że nie działa, gdy jest wiele poziomów zagnieżdżonych fragmentów
splinter123
Chmura, dajesz mi swoją architekturę zagnieżdżonych fragmentów? W mojej aplikacji to działa. Może coś mi brakuje.
林奕 忠
9
Kolejność otwierania backstacka nie jest taka, jak oczekiwałem w tym przykładzie. Uważam, że powinieneś najpierw znaleźć najgłębiej zagnieżdżony widoczny fragment w drzewie i spróbować przełamać na nim backstack, a następnie stopniowo przesuwać się w górę drzewa, szukając innych widocznych fragmentów, w których możesz zerwać backstack, aż go zdobędziesz. Szybką poprawką, która obsługuje najprostsze przypadki, jest zmiana funkcji onBackPressed (FragmentManager fm) i przełączenie bloku, który wyskakuje backstack, na ten, który wylicza fragmenty. Tym sposobem popping backstack głębiej w zagnieżdżonych fragmentów potomnych nastąpi pierwszy
Theo
1
Ponadto, gdy masz wiele zagnieżdżonych fragmentów, każdy ze swoimi fragmentami podrzędnymi, które mogą pokrywać część ekranu, pomysł posiadania oddzielnego stosu dla każdego menedżera fragmentów podrzędnych przestaje mieć sens. Backstack powinien być ujednolicony we wszystkich fragmentach w działaniu i śledzić zmiany fragmentów w kolejności, w jakiej zachodzą, niezależnie od poziomu zagnieżdżenia fragmentu.
Theo
Czy można zastosować to rozwiązanie bez, SupportFragmentManagera zamiast tego tylko Standard FragmentManager?
Peter Chappy
13

Z tą odpowiedzią zajmie się rekurencyjnym sprawdzaniem wstecznym i da każdemu fragmentowi szansę na zastąpienie domyślnego zachowania. Oznacza to, że możesz mieć fragment, w którym znajduje się ViewPager, wykonujący coś specjalnego, na przykład przewijanie do strony, która jest na stosie wstecznym, lub przewijanie do strony głównej, a następnie na następnym cofnięciu naciśnij exit.

Dodaj to do swojego działania, które rozszerza AppCompatActivity.

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

Dodaj to do swojego BaseFragment lub klasy, z której możesz dziedziczyć wszystkie swoje fragmenty.

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;
}
Szymon
źródło
to jest świetna odpowiedź, pracując na poziomie API 23/24 i przy wsparciu lib 24.1.1
Rinav
Używam tego od jakiegoś czasu, jest to świetne rozwiązanie - ale ostatnio ostrzeżenia o kompilacji dotyczące bezpośredniego dostępu do getFragments () z zewnątrz grupy bibliotek. Czy już zaktualizowałeś obejście?
Simon Huckett
czy coś zostało naprawione lub w 2020 roku nadal mamy problem i powinniśmy wdrożyć Twoje rozwiązanie ?? !!
Zhar
3

Ten kod będzie nawigował po drzewie menedżerów fragmentów i zwróci ostatni, który został dodany, zawierający fragmenty, które można wyskoczyć ze stosu:

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

Wywołaj metodę:

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

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

  super.onBackPressed();
}
AndroidDev
źródło
Działa to dla wszystkich zagnieżdżonych fragmentów, jeśli zostały poprawnie zarejestrowane !! +100
Pratik Saluja
2

Powodem jest to, że Twoja aktywność pochodzi od FragmentActivity, która obsługuje naciśnięcie klawisza BACK (zobacz wiersz 173 fragmentu FragmentActivity .

W naszej aplikacji używam ViewPagera (z fragmentami) i każdy fragment może mieć zagnieżdżone fragmenty. Sposób, w jaki sobie z tym poradziłem, polega na:

  • definiowanie interfejsu OnBackKeyPressedListener za pomocą jednej metody void onBackKeyPressed()
  • zaimplementowano ten interfejs we fragmentach „u góry”, które pokazuje ViewPager
  • nadpisywanie onKeyDown i wykrywanie BACK press i wywoływanie onBackKeyPressed w aktualnie aktywnym fragmencie na stronie widoku.

Zauważ również, że używam getChildFragmentManager()we fragmentach do prawidłowego zagnieżdżenia fragmentów. Możesz zobaczyć dyskusję i wyjaśnienie w tym poście dla programistów Androida .

miha
źródło
2

jeśli używasz fragmentu we fragmencie, użyj getChildFragmentManager()

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

jeśli używasz fragmentu potomnego i zamień go użyj getParentFragmentManager()

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

jeśli oba nie działają, spróbuj tego getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
Chetan
źródło
1

Byłem w stanie obsłużyć stos fragmentów, dodając do fragmentu nadrzędnego tę metodę w metodzie onCreate View () i przekazując widok główny.

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

Metoda isEnableFragmentBackStack () jest flagą logiczną, która informuje, kiedy znajduję się w głównym lub następnym fragmencie.

Upewnij się, że po zatwierdzeniu fragmentu, który ma mieć stos, musisz dodać metodę addToBackstack.

EkKoZ
źródło
1

To rozwiązanie może być lepsze, bo sprawdza wszystkie poziomy zagnieżdżonych fragmentów:

 /**
 * 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;
}

w swojej aktywności onBackPressedmożesz to po prostu nazwać w ten sposób:

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
źródło
1

Dziękuję wszystkim za pomoc, ta (poprawiona wersja) działa dla mnie:

@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;
}

UWAGA: Prawdopodobnie będziesz również chciał ustawić kolor tła tych zagnieżdżonych fragmentów na kolor tła okna motywu aplikacji, ponieważ domyślnie są one przezroczyste. Nieco poza zakresem tego pytania, ale można to osiągnąć, rozwiązując atrybut android.R.attr.windowBackground i ustawiając tło widoku fragmentu na ten identyfikator zasobu.

Steven L.
źródło
1

Ponad 5 lat i ten problem jest nadal aktualny. Jeśli nie chcesz używać funkcji fragmentManager.getFragments () ze względu na jej ograniczenie. Rozszerz i używaj poniższych klas:

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

Wyjaśnienie:

Każde działanie i fragment rozszerzony z powyższych klas zarządza własnym stosem tylnym dla każdego z ich dzieci i dzieci dzieci i tak dalej. Backstack to po prostu zapis tagów / identyfikatorów „aktywnych fragmentów”. Dlatego zastrzeżeniem jest zawsze dostarczanie tagu i / lub identyfikatora dla twojego fragmentu.

Dodając do backstack w childFragmentManager, będziesz musiał także wywołać „addToParentBackStack ()”. Gwarantuje to, że znacznik / identyfikator fragmentu zostanie dodany do nadrzędnego fragmentu / aktywności dla późniejszych wyskakujących okienek.

Przykład:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();
pościgowy
źródło
1

Zaimplementowałem to poprawnie, jeśli do tej pory nikt nie znalazł odpowiedzi

po prostu dodaj tę metodę do zagnieżdżonego fragmentu dziecka

   @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 );
}
Sunil Chaudhary
źródło
0

Rozwiązałem ten problem, zachowując aktualnie otwarty fragment we właściwościach działalności. Następnie nadpisuję metodę

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

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

W ten sposób dodaję fragment potomny do aktualnie otwartego fragmentu z samego siebie.

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

To jest układ xml fragmentu nadrzędnego i dodanego fragmentu

 <?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>
Michał Zhradnk Nono3551
źródło
0

Po zapoznaniu się z niektórymi przedstawionymi tutaj rozwiązaniami stwierdziłem, że aby zapewnić elastyczność i kontrolę nad fragmentem nadrzędnym co do tego, kiedy wyskakuje stos lub kiedy należy zignorować akcję wsteczną, raczej używam takiej implementacji:

Definiowanie interfejsu „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

}

Zastępowanie „onBackPressed” w działaniu nadrzędnym (lub w BaseActivity):

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

A następnie pozwól, aby fragment nadrzędny działał tak, jak sobie życzy, na przykład:

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
}

Pozwala to uniknąć myślenia, że ​​istnieje jakiś uzasadniony fragment potomny do usunięcia, na przykład podczas korzystania z pagera widoku (i liczba wpisów na stosie wstecznym> 0).

Czy ja
źródło
-1

Jeśli masz, DialogFragmentktóry z kolei ma zagnieżdżone fragmenty, „obejście” jest nieco inne. Zamiast ustawiać onKeyListenerna rootView, musisz to zrobić z Dialog. Będziesz także konfigurować jeden, DialogInterface.OnKeyListenera nie ten View. Oczywiście pamiętaj addToBackStack!

Przy okazji, posiadanie 1 fragmentu na backstacku do delegowania wywołania zwrotnego do działania jest moim osobistym przypadkiem użycia. Typowe scenariusze mogą mieć wartość 0.

Oto, co musisz zrobić w 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;
    }
Sachin Ahuja
źródło
Niezbyt zrozumiałe lub niepełne. DialogFragment lub jego fragment nadklasy nie mają elementu setOnKeyListener.
Ixx
@Ixx Musisz ustawić odbiornik na Dialog(nie DialogFragment), a odbiornik, którego używasz, to DialogInterface.OnKeyListener- kod działa i jest kompletny (i jest w użyciu). Dlaczego nie przedstawisz swojej sytuacji i może pomogę.
Sachin Ahuja
-1

w przypadku ChildFragments to działa.

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
user3854496
źródło