it-swarm.com.de

So setzen Sie Fragment aus BackStack fort, falls vorhanden

Ich lerne, Fragmente zu verwenden. Ich habe drei Instanzen von Fragment, die am Anfang 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 Ausschnitte funktionieren einwandfrei. Jedes Fragment ist an die Aktivität angehängt und wird problemlos im Back Stack gespeichert.

Wenn ich also A, C und dann B starte, sieht der Stack folgendermaßen aus:

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

Und wenn ich die 'Zurück'-Taste drücke, wird B zerstört und C wird fortgesetzt.

Wenn ich jedoch fragment A ein zweites Mal starte, wird es nicht vom Back-Stack wieder aufgenommen, sondern oben am Back-Stack hinzugefügt

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

Aber ich möchte A wieder aufnehmen und alle Fragmente darüber zerstören (falls vorhanden). Eigentlich mag ich einfach das standardmäßige Rückstapelverhalten. 

Wie mache ich das?

Erwartet: (A sollte wieder aufgenommen werden und die oberen Fragmente sollten zerstört werden)

| |
| |
| |
|A|
___

Edit: (vorgeschlagen von A - C)

Dies ist mein versuchender Code:

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 A und dann B starte, dann 'zurück' drücke, wird B entfernt und A wird fortgesetzt. Wenn Sie ein zweites Mal auf "Zurück" drücken, wird die App beendet. Es zeigt jedoch ein leeres Fenster und ich muss ein drittes Mal zurückdrücken, um es zu schließen.

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

Erwartet:

| |
| |
|B|
|A|
___

Tatsächlich:

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

Sollte ich onBackPressed() mit einer Anpassung überschreiben oder fehlt mir etwas?

123
Kaidul

Wenn Sie die documentation lesen, können Sie den Back-Stack anhand des Transaktionsnamens oder der von commit bereitgestellten ID abrufen. Die Verwendung des Namens may ist einfacher, da es nicht erforderlich sein sollte, eine Zahl zu verfolgen, die sich möglicherweise ändert, und die Logik des "einzigartigen Backstack-Eintrags" verstärkt.

Da Sie pro Fragment nur einen Back-Stack-Eintrag wünschen, machen Sie den Back-State-Namen zum Klassennamen des Fragments (über getClass().getName()). Wenn Sie eine Fragment-Komponente ersetzen, verwenden Sie die Methode popBackStackImmediate() . Wenn true zurückgegeben wird, bedeutet dies, dass sich eine Instanz des Fragments im hinteren Stapel befindet. Wenn nicht, führen Sie die Fragmentersetzungslogik 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();
  }
}

EDIT

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

Dies liegt daran, dass FragmentTransaction dem hinteren Stapel hinzugefügt wird, um sicherzustellen, dass die Fragmente später oben abgelegt werden können. Eine schnelle Lösung hierfür ist das Überschreiben von onBackPressed() und das Beenden der Aktivität, wenn der Backstack nur 1 Fragment enthält. 

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

In Bezug auf die doppelten Back-Stack-Einträge ist Ihre Bedingungsanweisung, die das Fragment ersetzt, wenn es nicht geknackt wurde, eindeutig abweichend als das meines ursprünglichen Codeausschnitts. Was Sie tun, ist das Hinzufügen zum hinteren Stapel, unabhängig davon, ob der hintere Stapel geknackt 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 das selbe Fragment ausgewählt wurde, während es sichtbar war, was ebenfalls zu doppelten Einträgen führte.

Implementierung:

Ich empfehle Ihnen dringend, die aktualisierte replaceFragment()-Methode nicht wie in Ihrem Code auseinander zu nehmen. Die gesamte Logik ist in dieser Methode enthalten, und bewegliche Teile können Probleme verursachen.

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

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 ein Fragment akzeptiert und die Klassennamen vergleicht. Wenn etwas passt, ändern Sie den Titel und die Auswahl. Fügen Sie auch ein OnBackStackChangedListener hinzu und lassen Sie Ihre Aktualisierungsmethode aufrufen, wenn ein gültiges Fragment vorhanden ist.

Fügen Sie beispielsweise in onCreate() der Aktivität 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
  }
}

Immer wenn sich der hintere Stapel ändert, spiegeln der Titel und die überprüfte Position das sichtbare Fragment wider.

251
A--C

Ich denke, diese Methode löst mein Problem: 

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


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    manager.findFragmentByTag ( tag );
    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 ();
    }
}

das war ursprünglich in Diese Frage

6
erluxman

Schritt 1: Implementiere eine Schnittstelle mit deiner 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();
3
Vinod Joshi

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 in Ihrem XML-Layout verwenden 

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

Clickable bedeutet, dass es von einem Zeigergerät angeklickt oder von einem Touchgerät angetippt werden kann.

Focusable bedeutet, dass der Fokus von einem Eingabegerät wie einer Tastatur erhalten kann. Eingabegeräte wie Tastaturen können auf der Grundlage der Eingaben selbst nicht entscheiden, an welche Ansicht die Eingabeereignisse gesendet werden.

0
Hossam Hassan

Ich weiß, dass dies für die Beantwortung dieser Frage ziemlich spät ist, aber ich habe dieses Problem selbst gelöst und es für angebracht gehalten, 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();
    }
}
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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