So setzen Sie Fragment von BackStack fort, falls vorhanden

151

Ich lerne, wie man Fragmente benutzt. Ich habe drei Instanzen davon Fragment, die oben in der Klasse initialisiert werden. Ich füge das Fragment einer Aktivität wie dieser hinzu:

Deklarieren und Initialisieren:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Ersetzen / Hinzufügen:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Diese Schnipsel funktionieren ordnungsgemäß. Jedes Fragment ist an die Aktivität angehängt und wird problemlos im Backstack gespeichert.

Also , wenn ich starten A, Cund dann Bdie Stapel sieht wie folgt aus :

| |
|B|
|C|
|A|
___

Und wenn ich den 'Zurück'-Knopf drücke, Bwird zerstört und Cwieder aufgenommen.

Wenn ich das Fragment jedoch Aein zweites Mal starte, anstatt es vom Backstack fortzusetzen, wird es oben im Backstack hinzugefügt

| |
|A|
|C|
|A|
___

Aber ich möchte Aalle Fragmente darüber (falls vorhanden) wieder aufnehmen und zerstören. Eigentlich gefällt mir nur das Standardverhalten des Backstacks.

Wie schaffe ich das?

Erwartet: ( Asollte wieder aufgenommen werden und obere Fragmente sollten zerstört werden)

| |
| |
| |
|A|
___

Bearbeiten: (vorgeschlagen von A - C)

Dies ist mein Versuchscode:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Das Problem ist, wenn ich starte Aund dann B'Zurück' drücke, Bwird es entfernt und Afortgesetzt. Wenn Sie ein zweites Mal auf "Zurück" klicken, wird die App beendet. Aber es zeigt ein leeres Fenster und ich muss ein drittes Mal zurück drücken, um es zu schließen.

Auch wenn ich starte A, dann B, dann C, dann Bwieder ...

Erwartet:

| |
| |
|B|
|A|
___

Tatsächlich:

| |
|B|
|B|
|A|
___

Sollte ich onBackPressed()Anpassungen vornehmen oder fehlt mir etwas?

Kaidul
quelle

Antworten:

279

Beim Lesen der Dokumentation gibt es eine Möglichkeit, den Backstack basierend auf dem Transaktionsnamen oder der von Commit bereitgestellten ID zu platzieren. Die Verwendung des Namens kann einfacher sein, da es nicht erforderlich sein sollte, eine Zahl zu verfolgen, die sich ändern und die Logik des "eindeutigen Backstack-Eintrags" verstärken kann.

Da Sie nur einen Backstack-Eintrag pro möchten Fragment, machen Sie den Back-State-Namen zum Klassennamen des Fragments (via getClass().getName()). FragmentVerwenden Sie dann beim Ersetzen von a die popBackStackImmediate()Methode. Wenn true zurückgegeben wird, befindet sich eine Instanz des Fragments im Backstack. Wenn nicht, führen Sie die Fragmentersetzungslogik tatsächlich aus.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

BEARBEITEN

Das Problem ist - wenn ich A und dann B starte und dann die Zurück-Taste drücke, wird B entfernt und A wird fortgesetzt. Wenn Sie die Zurück-Taste erneut drücken, wird die App beendet. Es wird jedoch ein leeres Fenster angezeigt, und Sie müssen erneut drücken, um es zu schließen.

Dies liegt daran, dass das dem FragmentTransactionBackstack hinzugefügt wird, um sicherzustellen, dass wir die Fragmente später oben platzieren können. Eine schnelle Lösung hierfür ist das Überschreiben onBackPressed()und Beenden der Aktivität, wenn der Backstack nur 1 enthältFragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

In Bezug auf die doppelten Backstack-Einträge ist Ihre bedingte Anweisung, die das Fragment ersetzt, wenn es nicht gelöscht wurde, eindeutig anders als das, was mein ursprünglicher Code - Schnipsel. Was Sie tun, ist das Hinzufügen zum Backstack, unabhängig davon, ob der Backstack gelöscht wurde oder nicht.

So etwas sollte näher an dem sein, was Sie wollen:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

Die Bedingung wurde ein wenig geändert, da die Auswahl des gleichen Fragments, während es sichtbar war, auch doppelte Einträge verursachte.

Implementierung:

Ich empfehle dringend, das aktualisierte nicht zu nehmen replaceFragment() Methode nicht wie in Ihrem Code auseinander zu nehmen. Die gesamte Logik ist in dieser Methode enthalten, und das Bewegen von Teilen kann Probleme verursachen.

Dies bedeutet, dass Sie die aktualisierte replaceFragment()Methode in Ihre Klasse kopieren und dann ändern sollten

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

so ist es einfach

replaceFragment (fragmentName);

EDIT # 2

Um die Schublade zu aktualisieren, wenn sich der Backstack ändert, erstellen Sie eine Methode, die in einem Fragment akzeptiert und die Klassennamen vergleicht. Wenn etwas übereinstimmt, ändern Sie den Titel und die Auswahl. Fügen Sie auch eine hinzuOnBackStackChangedListener und lassen Sie es Ihre Aktualisierungsmethode aufrufen, wenn ein gültiges Fragment vorhanden ist.

Fügen Sie beispielsweise in den Aktivitäten onCreate()hinzu

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

Und die andere Methode:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Wenn sich der hintere Stapel ändert, spiegeln der Titel und die aktivierte Position das Sichtbare wider Fragment.

A - C.
quelle
Danke für deine Antwort. :) Es funktioniert teilweise. Ich habe meine Frage mit Fortschritt bearbeitet. Bitte werfen Sie einen Blick)
Kaidul
2
@RishabhSrivastava Denken Sie daran, dass ein "Pop" normalerweise das oberste Fragment zerstört. Welche Methode zuerst aufgerufen wird, hängt davon ab - sehen Sie sich den Fragment-Lebenszyklus an. Für Ihren Fall würde ich ein verwenden, OnBackstackChangedListenerdamit Sie garantieren können, dass Sie mit dem richtigen Fragment arbeiten.
A - C
2
@RishabhSrivastava Deshalb habe ich auch das vorgeschlagen OnBackstackChangedListener.
A - C
1
@ AlexanderFarber Du hast recht. Als ich diese Antwort gab, dachte ich nicht darüber nach instanceof:)
A - C
2
Ihre Lösung klingt gut, aber wenn Sie drei Fragmente haben und darauf klicken A-B-C-Bund einmal zurück drücken, kehren Sie zurück zu Aund nicht zurück zu C. Das liegt daran, dass popBackStackImmediatenicht nur das angegebene Tag, sondern auch alles darüber angezeigt wird. Gibt es dort irgendwelche Arbeiten?
Raeglan
10

Ich denke, diese Methode kann Ihr Problem lösen:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

welches ursprünglich in dieser Frage gepostet wurde

Erluxman
quelle
Bitte lassen Sie mich wissen, wenn eine solche Antwort gegen die Regeln verstößt, die ich den Leuten in diesem Beitrag nur leicht machen wollte. Danke ..
Erluxman
Ich bin nicht sicher, ob es gegen die Regeln verstößt (wahrscheinlich nicht), aber das ist definitiv hilfreich
Kathir
1
Was nützt die dritte Zeile? -> manager.findFragmentByTag (Tag); Sieht so aus, als würde es nichts tun.
Rik van Velzen
3

Schritt 1: Implementieren Sie eine Schnittstelle zu Ihrer Aktivitätsklasse

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Schritt 2: Wenn Sie ein anderes Fragment aufrufen, fügen Sie diese Methode hinzu:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();
Vinod Joshi
quelle
2

Ich weiß, dass es ziemlich spät ist, diese Frage zu beantworten, aber ich habe dieses Problem selbst gelöst und dachte, es lohnt sich, es mit allen zu teilen. "

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

Und in der onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}
Vivek Pratap Singh
quelle
1
@ X-Black ... BaseFragment ist eine Basisklasse für jedes in der Anwendung verwendete Fragment.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});
Gushedaoren
quelle
0

Eine einfachere Lösung wird diese Linie ändern

ft.replace(R.id.content_frame, A); zu ft.add(R.id.content_frame, A);

Und bitte verwenden Sie in Ihrem XML-Layout

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable bedeutet, dass es von einem Zeigergerät angeklickt oder von einem Touch-Gerät getippt werden kann.

Focusablebedeutet, dass es den Fokus von einem Eingabegerät wie einer Tastatur erhalten kann. Eingabegeräte wie Tastaturen können anhand der Eingaben selbst nicht entscheiden, an welche Ansicht ihre Eingabeereignisse gesendet werden sollen. Sie senden sie daher an die Ansicht mit Fokus.

Hossam Hassan
quelle