Android 4.2: Backstack-Verhalten mit verschachtelten Fragmenten


108

Mit Android 4.2 erhielt die Support-Bibliothek Unterstützung für verschachtelte Fragmente, siehe hier . Ich habe damit herumgespielt und ein interessantes Verhalten / einen Fehler in Bezug auf Backstack und getChildFragmentManager () gefunden . Bei Verwendung von getChildFragmentManager () und addToBackStack (String name) führt das System durch Drücken der Zurück-Taste den Back-Stack nicht zum vorherigen Fragment herunter. Wenn Sie dagegen getFragmentManager () und addToBackStack (String name) verwenden, kehrt das System durch Drücken der Zurück-Taste zum vorherigen Fragment zurück.

Für mich ist dieses Verhalten unerwartet. Durch Drücken der Zurück-Taste auf meinem Gerät erwarte ich, dass das zuletzt dem Back-Stack hinzugefügte Fragment gelöscht wird, selbst wenn das Fragment dem Back-Stack im Kinderfragment-Manager hinzugefügt wurde.

Ist dieses Verhalten korrekt? Ist dieses Verhalten ein Fehler? Gibt es eine Lösung für dieses Problem?

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

}

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

}

hmmm das ist sehr interessant. Ich würde dieses Verhalten auch nicht erwarten. Wenn dies der Fall ist, scheint es, dass wir OnBackPressed überschrieben haben und bei dieser Methode das Fragment mit untergeordneten Fragmenten abrufen und dann den ChildFragmentManager abrufen, um eine Pop-Transaktion auszuführen.
Marco RS

@ Marco, genau, aber es ist eine Problemumgehung. Warum funktioniert die API nicht richtig?
Egor

Antworten:


67

Diese Lösung ist möglicherweise eine bessere Version der @ Sean-Antwort:

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

Wieder habe ich diese Lösung basierend auf der obigen @ Sean-Antwort vorbereitet.

Wie @ AZ13 sagte, ist diese Lösung nur in Situationen mit untergeordneten Fragmenten auf einer Ebene möglich. Im Fall von Fragmenten mit mehreren Ebenen werden die Arbeiten etwas komplexer. Ich empfehle daher, diese Lösung nur in dem von mir genannten realisierbaren Fall auszuprobieren. =)

Hinweis: Da die getFragmentsMethode jetzt eine private Methode ist, funktioniert diese Lösung nicht. Sie können in den Kommentaren nach einem Link suchen, der eine Lösung für diese Situation vorschlägt.


2
林奕 忠 Antwort sollte anstelle dieser verwendet werden - es behandelt korrekt mehrere verschachtelte Fragmente
Hameno

Funktioniert der Code wirklich? Auch getFragments ist keine öffentliche Methode? stackoverflow.com/a/42572412/4729203
wonsuc

Es hat einmal funktioniert, funktioniert aber jetzt möglicherweise nicht mehr, wenn getFragments nicht mehr öffentlich ist.
ismailarilik

1
getFragmentsist jetzt öffentlich.
Grrigore

Dies funktioniert nicht richtig, wenn Sie FragA -> Frag B -> Frag C in Pager haben ...
Zhar

57

Scheint wie ein Fehler. Schauen Sie sich an: http://code.google.com/p/android/issues/detail?id=40323

Für eine Problemumgehung, die ich erfolgreich verwendet habe (wie in den Kommentaren vorgeschlagen):

    @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
Leider ist dies keine richtige Lösung und nur eine Problemumgehung für den einfachsten Fall. Nehmen Sie einfach an, Sie haben ein Fragment, das ein anderes Fragment enthält, das ein anderes Fragment enthält und so weiter. Dann müssen Sie den Aufruf bis zum letzten Fragment weitergeben, das ein anderes oder mehrere Fragmente enthält: /
AZ13

@ MuhammadBabar Nein, wirst du nicht. Wenn es eine Null ist, wird der zweite Teil der Anweisung nicht ausgewertet, da er bereits als falsch bestimmt wurde. Der Operand ist & und nicht | .
Sean

25

Die eigentliche Antwort auf diese Frage ist die Funktion setPrimaryNavigationFragment der Fragmenttransaktion.

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

Sie müssen diese Funktion für das ursprüngliche übergeordnete Fragment festlegen, wenn die Aktivität es hinzufügt. Ich habe eine replaceFragment-Funktion in meiner Aktivität, die folgendermaßen aussieht:

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

Dies gibt Ihnen das gleiche Verhalten, als würden Sie von normalem Fragment B zurück zu Fragment A klicken, außer jetzt ist es auch auf den untergeordneten Fragmenten!


3
Toller Fund! Dies löst das vom OP erwähnte Problem auf eine viel sauberere und weniger hackige Weise.
Jona

Das stürzt ab, es sei denn, es ist Teil der Navigationsbibliothek als HavHostFragment
Didi

22

Diese Lösung ist möglicherweise eine bessere Version von @ismailarilik. Antwort:

Verschachtelte Fragmentversion

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

es scheint nicht zu arbeiten , wenn es mehrere Ebenen von verschachtelten Fragmente sind
splinter123

Cloud Sie geben mir Ihre Architektur von verschachtelten Fragmenten? In meiner App funktioniert es. Vielleicht vermisse ich etwas.
林奕 忠

9
Die Reihenfolge für das Aufspringen des Backstacks entspricht nicht den Erwartungen in diesem Beispiel. Ich glaube, Sie sollten zuerst das am tiefsten verschachtelte sichtbare Fragment im Baum finden und versuchen, den Backstack darauf zu platzieren, und dann schrittweise den Baum nach oben bewegen, um nach anderen sichtbaren Fragmenten zu suchen, in denen Sie den Backstack platzen lassen können, bis Sie eines erhalten. Eine schnelle Lösung für die einfachsten Fälle besteht darin, die Funktion onBackPressed (FragmentManager fm) zu ändern und den Block, der den Backstack öffnet, durch den Block zu ersetzen, der Fragmente auflistet. Auf diese Weise wird der Backstack zuerst in tiefer verschachtelte Kinderfragmente geknallt
Theo

1
Wenn Sie viele verschachtelte Fragmente mit jeweils untergeordneten Fragmenten haben, die möglicherweise einen Teil des Bildschirms abdecken, ist die Idee, für jeden untergeordneten Fragmentmanager einen separaten Backstack zu haben, nicht mehr sinnvoll. Der Backstack sollte über alle Fragmente in der Aktivität hinweg einheitlich sein und Fragmentänderungen in der Reihenfolge verfolgen, in der sie auftreten, unabhängig von der Verschachtelungsebene des Fragments.
Theo

Ist es möglich, diese Lösung ohne den SupportFragmentManagerund stattdessen nur den Standard zu verwenden FragmentManager?
Peter Chappy

13

Mit dieser Antwort wird die rekursive Rückprüfung durchgeführt und jedem Fragment die Möglichkeit gegeben, das Standardverhalten zu überschreiben. Dies bedeutet, dass Sie ein Fragment, das einen ViewPager hostet, dazu bringen können, etwas Besonderes zu tun, z. B. zu der Seite zu scrollen, die als Backstack dient, oder zur Startseite zu scrollen und dann beim nächsten Zurück drücken Sie Beenden.

Fügen Sie dies Ihrer Aktivität hinzu, die AppCompatActivity erweitert.

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

Fügen Sie dies Ihrem BaseFragment oder der Klasse hinzu, von der Sie alle Ihre Fragmente erben lassen können.

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

Dies ist eine großartige Antwort, die auf API-Ebene 23/24 und mit Unterstützung lib 24.1.1
Rinav

Ich benutze dies schon seit einiger Zeit, es ist eine großartige Lösung - aber vor kurzem wurden Warnungen über den direkten Zugriff auf getFragments () von außerhalb der Bibliotheksgruppe kompiliert. Haben Sie bereits mit einer Problemumgehung aktualisiert?
Simon Huckett

Ist etwas behoben oder im Jahr 2020 haben wir noch das Problem und sollten Ihre Lösung implementieren ?? !!
Zhar

3

Dieser Code navigiert durch den Baum der Fragmentmanager und gibt den zuletzt hinzugefügten zurück, der alle Fragmente enthält, die vom Stapel entfernt werden können:

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

Rufen Sie die Methode auf:

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

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

  super.onBackPressed();
}

Dies funktioniert für alle verschachtelten Fragmente, wenn es korrekt registriert ist !! +100
Pratik Saluja

2

Der Grund dafür ist, dass Ihre Aktivität von FragmentActivity abgeleitet ist, die den Tastendruck BACK verarbeitet (siehe Zeile 173 von FragmentActivity) .

In unserer Anwendung verwende ich einen ViewPager (mit Fragmenten) und jedes Fragment kann verschachtelte Fragmente haben. Ich habe damit umgegangen:

  • Definieren einer Schnittstelle OnBackKeyPressedListener mit einer einzigen Methode void onBackKeyPressed()
  • hat diese Schnittstelle in "Top" -Fragmenten implementiert, die ViewPager anzeigt
  • Überschreiben von onKeyDown und Erkennen von BACK-Drücken und Aufrufen von onBackKeyPressed in einem aktuell aktiven Fragment im Ansichtspager.

Beachten Sie auch, dass ich getChildFragmentManager()in den Fragmenten Fragmente richtig verschachtele. Sie können eine Diskussion und eine Erklärung in diesem Beitrag von Android-Entwicklern sehen .


2

Wenn Sie Fragment in einem Fragment verwenden, verwenden Sie getChildFragmentManager()

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

Wenn Sie ein untergeordnetes Fragment verwenden und es ersetzen, verwenden Sie getParentFragmentManager()

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

Wenn beide nicht für Sie arbeiten, versuchen Sie dies getActivity().getSupportFragmentManager()

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

1

Ich war in der Lage, Fragment-Back-Stack zu verarbeiten, indem ich diese Methode bei der onCreate View () -Methode zum übergeordneten Fragment hinzufügte und die Stammansicht übergab.

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

Die Methode isEnableFragmentBackStack () ist ein boolesches Flag, um zu wissen, wann ich mich auf dem Hauptfragment oder dem nächsten befinde.

Stellen Sie sicher, dass Sie beim Festschreiben des Fragments, für das ein Stapel vorhanden sein muss, die Methode addToBackstack hinzufügen müssen.


1

Diese Lösung ist möglicherweise besser, da sie alle Ebenen verschachtelter Fragmente überprüft:

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

In Ihrer Aktivität können onBackPressedSie es einfach so nennen:

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

Vielen Dank an alle für ihre Hilfe, diese (optimierte Version) funktioniert für mich:

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

HINWEIS: Möglicherweise möchten Sie auch die Hintergrundfarbe dieser verschachtelten Fragmente auf die Hintergrundfarbe des Fensterfensters des App-Themas einstellen, da diese standardmäßig transparent sind. Etwas außerhalb des Rahmens dieser Frage, aber dies wird erreicht, indem das Attribut android.R.attr.windowBackground aufgelöst und der Hintergrund der Fragmentansicht auf diese Ressourcen-ID gesetzt wird.


1

Mehr als 5 Jahre und dieses Thema ist immer noch relevant. Wenn Sie fragmentManager.getFragments () aufgrund seiner Einschränkung nicht verwenden möchten. Erweitern und verwenden Sie die folgenden Klassen:

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

Erläuterung:

Jede Aktivität und jedes Fragment, die aus den oben genannten Klassen erweitert wurden, verwaltet ihren eigenen Backstack für jedes ihrer Kinder und Kinderkinder und so weiter. Der Backstack ist einfach eine Aufzeichnung von "aktiven Fragment" -Tags / IDs. Die Einschränkung besteht also darin, immer ein Tag und / oder eine ID für Ihr Fragment bereitzustellen.

Wenn Sie in einem childFragmentManager zum Backstack hinzufügen, müssen Sie auch "addToParentBackStack ()" aufrufen. Dadurch wird sichergestellt, dass das Tag / die ID des Fragments für spätere Popups zum übergeordneten Fragment / zur übergeordneten Aktivität hinzugefügt wird.

Beispiel:

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

1

Ich habe es richtig implementiert, wenn jemand bis jetzt keine Antwort gefunden hat

Fügen Sie diese Methode einfach zu Ihrem untergeordneten verschachtelten Fragment hinzu

   @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

Ich habe dieses Problem gelöst, indem ich das aktuell geöffnete Fragment im Eigentum der Aktivität gehalten habe. Dann überschreibe ich die Methode

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

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

Auf diese Weise füge ich ein untergeordnetes Fragment aus sich selbst in das aktuell geöffnete Fragment ein.

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

Dies ist das XML-Layout des übergeordneten Fragments und des hinzugefügten Fragments

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

Nachdem ich einige der hier vorgestellten Lösungen beobachtet hatte, stellte ich fest, dass ich eher eine solche Implementierung verwende, um dem übergeordneten Fragment Flexibilität und Kontrolle darüber zu ermöglichen, wann der Stapel geöffnet wird oder wann die Rückaktion ignoriert werden soll:

Definieren einer "ParentFragment" -Schnittstelle:

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

}}

Überschreiben von "onBackPressed" in der übergeordneten Aktivität (oder in der BaseActivity):

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

Lassen Sie dann das übergeordnete Fragment so behandeln, wie es möchte, zum Beispiel:

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
}

Auf diese Weise können Sie vermeiden, dass Sie glauben, dass ein legitimes untergeordnetes Fragment entfernt werden muss, wenn Sie beispielsweise einen Ansichtspager verwenden (und die Anzahl der Backstack-Einträge> 0).


-1

Wenn Sie eine haben, DialogFragmentdie wiederum verschachtelte Fragmente enthält, ist die 'Problemumgehung' etwas anders. Anstatt ein onKeyListenerauf das zu setzen rootView, müssen Sie dies mit dem tun Dialog. Außerdem werden Sie eine DialogInterface.OnKeyListenerund nicht die Vieweine einrichten. Natürlich, denk dran addToBackStack!

Übrigens ist es mein persönlicher Anwendungsfall, 1 Fragment im Backstack zu haben, um den Rückruf an die Aktivität zu delegieren. Typische Szenarien könnten sein, dass die Anzahl 0 ist.

Folgendes müssen Sie im onCreateDialog tun

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

Nicht wirklich verständlich oder nicht vollständig. DialogFragment oder sein Superklassenfragment haben keinen setOnKeyListener.
Ixx

@Ixx Sie müssen den Listener aktivieren Dialog(nicht DialogFragment) und der von Ihnen verwendete Listener ist DialogInterface.OnKeyListener- der Code funktioniert und ist vollständig (und wird verwendet). Warum geben Sie nicht Ihre Situation an und vielleicht kann ich helfen.
Sachin Ahuja

-1

für ChildFragments funktioniert das ..

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.