web-dev-qa-db-de.com

Wie kann ich den Fragmentstatus beibehalten, wenn ich ihn dem Backstack hinzufüge?

Ich habe eine Dummy-Aktivität geschrieben, die zwischen zwei Fragmenten wechselt. Wenn Sie von FragmentA nach FragmentB wechseln, wird FragmentA dem Backstack hinzugefügt. Wenn ich jedoch zu FragmentA zurückkehre (durch Drücken der Taste "Zurück"), wird ein völlig neues FragmentA erstellt und der Zustand, in dem es sich befand, geht verloren. Ich habe das Gefühl, ich bin hinter der Frage this her, aber ich habe ein vollständiges Codebeispiel beigefügt, um das Problem zu beheben:

public class FooActivity extends Activity {
  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(Android.R.id.content, new FragmentA());
    transaction.commit();
  }

  public void nextFragment() {
    final FragmentTransaction transaction = getFragmentManager().beginTransaction();
    transaction.replace(Android.R.id.content, new FragmentB());
    transaction.addToBackStack(null);
    transaction.commit();
  }

  public static class FragmentA extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      final View main = inflater.inflate(R.layout.main, container, false);
      main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
          ((FooActivity) getActivity()).nextFragment();
        }
      });
      return main;
    }

    @Override public void onSaveInstanceState(Bundle outState) {
      super.onSaveInstanceState(outState);
      // Save some state!
    }
  }

  public static class FragmentB extends Fragment {
    @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
      return inflater.inflate(R.layout.b, container, false);
    }
  }
}

Mit einigen hinzugefügten Protokollnachrichten:

07-05 14:28:59.722 D/OMG     ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG     ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG     ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG     ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG     ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG     ( 1260): FragmentA.onCreateView

Es ruft niemals FragmentA.onSaveInstanceState auf und erstellt ein neues FragmentA, wenn Sie zurückschlagen. Wenn ich mich jedoch in FragmentA befinde und den Bildschirm sperre, wird FragmentA.onSaveInstanceState aufgerufen. So seltsam ... irre ich mich, wenn ich erwarte, dass ein Fragment, das dem Backstack hinzugefügt wurde, nicht neu erstellt werden muss? Hier ist, was die docs sagen:

Wenn Sie hingegen beim Entfernen eines Fragments addToBackStack () aufrufen, wird das Fragment angehalten und fortgesetzt, wenn der Benutzer zurück navigiert.

147
Eric

Wenn Sie vom Backstack zu einem Fragment zurückkehren, wird das Fragment nicht neu erstellt, sondern dieselbe Instanz erneut verwendet und mit onCreateView() im Fragment-Lebenszyklus begonnen (siehe Fragment-Lebenszyklus ) .

Wenn Sie also den Status speichern möchten, sollten Sie Instanzvariablen verwenden und sich nicht auf onSaveInstanceState() verlassen.

114
Jan-Henk

Im Vergleich zu Apples UINavigationController und UIViewController schneidet Google in der Android) - Software-Architektur nicht gut ab, und das Android-Dokument über Fragment hilft nicht viel .

Wenn Sie FragmentB über FragmentA eingeben, wird die vorhandene FragmentA-Instanz nicht zerstört. Wenn Sie in FragmentB auf Zurück klicken und zu FragmentA zurückkehren, wird keine neue FragmentA-Instanz erstellt. Die onCreateView() der vorhandenen FragmentA-Instanz wird aufgerufen.

Das Wichtigste ist, dass wir die Ansicht in FragmentAs onCreateView() nicht erneut vergrößern sollten, da wir die vorhandene Instanz von FragmentA verwenden. Wir müssen die rootView speichern und wiederverwenden.

Der folgende Code funktioniert gut. Es behält nicht nur den Fragmentstatus bei, sondern reduziert auch die RAM und CPU-Auslastung (da das Layout nur bei Bedarf aufgeblasen wird). Ich kann nicht glauben, dass der Beispielcode und das Dokument von Google dies niemals erwähnen, aber Layout immer aufblasen .

Version 1 (Verwenden Sie nicht Version 1. Verwenden Sie Version 2)

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // (it will be added back).
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        return _rootView;
    }
}

------ Update am 3. Mai 2005: -------

Wie in den Kommentaren erwähnt, ist _rootView.getParent() in onCreateView manchmal null, was den Absturz verursacht. Version 2 entfernt _rootView in onDestroyView (), wie von Dell116 vorgeschlagen. Getestet auf Android 4.0.3, 4.4.4, 5.1.0.

Version 2

public class FragmentA extends Fragment {
    View _rootView;
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (_rootView == null) {
            // Inflate the layout for this fragment
            _rootView = inflater.inflate(R.layout.fragment_a, container, false);
            // Find and setup subviews
            _listView = (ListView)_rootView.findViewById(R.id.listView);
            ...
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove _rootView from the existing parent view group
            // in onDestroyView() (it will be added back).
        }
        return _rootView;
    }

    @Override
    public void onDestroyView() {
        if (_rootView.getParent() != null) {
            ((ViewGroup)_rootView.getParent()).removeView(_rootView);
        }
        super.onDestroyView();
    }
}

ACHTUNG !!!

Dies ist ein HACK! Obwohl ich es in meiner App verwende, müssen Sie die Kommentare sorgfältig testen und lesen.

79
Vince Yuan

Ich denke, es gibt eine Alternative, um das zu erreichen, wonach Sie suchen. Ich sage nicht, dass es eine vollständige Lösung ist, aber es hat in meinem Fall den Zweck erfüllt.

Anstatt das Fragment zu ersetzen, habe ich gerade ein Zielfragment hinzugefügt. Im Grunde werden Sie add() Methode statt replace() verwenden.

Was ich sonst noch getan habe. Ich verstecke mein aktuelles Fragment und füge es dem Backstack hinzu.

Daher überlappt es ein neues Fragment mit dem aktuellen Fragment, ohne seine Ansicht zu zerstören. (Überprüfen Sie, ob die onDestroyView() -Methode aufgerufen wird. Wenn ich es zu backstate hinzufüge, habe ich den Vorteil, das Fragment fortzusetzen.

Hier ist der Code:

Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
Android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();

AFAIK System ruft onCreateView() nur auf, wenn die Ansicht zerstört oder nicht erstellt wurde. Aber hier haben wir die Ansicht gespeichert, indem wir sie nicht aus dem Speicher entfernt haben. Es wird also keine neue Ansicht erstellt.

Und wenn Sie vom Zielfragment zurückkehren, wird das letzte FragmentTransaction entfernte obere Fragment eingefügt, wodurch die oberste Ansicht (SourceFragment) über dem Bildschirm angezeigt wird.

KOMMENTAR: Wie gesagt, es ist keine vollständige Lösung, da die Ansicht des Quellfragments nicht entfernt wird und daher mehr Speicher als gewöhnlich belegt wird. Aber trotzdem, diene dem Zweck. Außerdem verwenden wir einen völlig anderen Mechanismus zum Ausblenden von Ansichten, anstatt sie zu ersetzen, der nicht traditionell ist.

Es hängt also nicht wirklich davon ab, wie Sie den Status beibehalten, sondern davon, wie Sie die Ansicht beibehalten.

49
kaushal trivedi

Ich bin auf dieses Problem in einem Fragment gestoßen, das eine Karte enthält, die zu viele Einrichtungsdetails zum Speichern/Neuladen enthält. Meine Lösung bestand darin, dieses Fragment die ganze Zeit aktiv zu halten (ähnlich wie bei @kaushal).

Angenommen, Sie haben das aktuelle Fragment A und möchten Fragment B anzeigen. Zusammenfassen der Konsequenzen:

  • replace () - Fragment A entfernen und durch Fragment B ersetzen. Fragment A wird neu erstellt, sobald es wieder in den Vordergrund gebracht wird
  • add () create and) fügt ein Fragment B hinzu und überlappt Fragment A, das noch im Hintergrund aktiv ist
  • remove () - kann verwendet werden, um Fragment B zu entfernen und zu A zurückzukehren. Fragment B wird neu erstellt, wenn es später aufgerufen wird

Wenn Sie beide Fragmente "gespeichert" lassen möchten, können Sie sie einfach mit hide ()/show () umschalten.

--- (Profis: einfache Methode, um mehrere Fragmente am Laufen zu halten
Cons: Sie verwenden viel mehr Arbeitsspeicher, um sie alle am Laufen zu halten. Kann auf Probleme stoßen, z.B. Anzeige vieler großer Bitmaps

6
Teo Inke

onSaveInstanceState() wird nur bei Konfigurationsänderungen aufgerufen.

Seit dem Wechsel von einem Fragment zu einem anderen gibt es keine Konfigurationsänderung, so dass kein Aufruf von onSaveInstanceState() vorhanden ist. Welcher Staat wird nicht gerettet? Können Sie angeben?

Wenn Sie Text in EditText eingeben, wird dieser automatisch gespeichert. Jedes UI-Element ohne ID ist das Element, dessen Ansichtsstatus nicht gespeichert werden soll.

3
user2779311

Da onSaveInstanceState in fragment nicht aufgerufen wird, wenn Sie Fragment in den Backstack einfügen. Der Fragment-Lebenszyklus im Backstack bei Wiederherstellung beginnt onCreateView und endet onDestroyView, während onSaveInstanceState zwischen onDestroyView und onDestroy aufgerufen wird. Meine Lösung ist create instance variable und init in onCreate. Beispielcode:

private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
     super.onCreate(savedInstanceState);
     isDataLoading = false;
     // init list at once when create fragment
     listData = new ArrayList();
}

Und überprüfe es in onActivityCreated:

public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if(isDataLoading){
         fetchData();
    }else{
         //get saved instance variable listData()
    }
}

private void fetchData(){
     // do fetch data into listData
}
0
Ling Boo

first: benutze einfach add method anstelle von replace method der FragmentTransaction Klasse, dann musst du secondFragment hinzufügen, um mit addToBackStack zu stapeln

second: beim Rückklick musst du popBackStackImmediate () aufrufen

Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();

((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
        @Override
        public void backFragmentNewsResult()
        {                                    
            getChildFragmentManager().popBackStackImmediate();                                
        }
};
0
MiladAhmadi
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
    {
        @Override
        public void onBackStackChanged()
        {
            if (getSupportFragmentManager().getBackStackEntryCount() == 0)
            {
                //setToolbarTitle("Main Activity");
            }
            else
            {
                Log.e("fragment_replace11111", "replace");
            }
        }
    });


YourActivity.Java
@Override
public void onBackPressed()
{
 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
  if (fragment instanceof YourFragmentName)
    {
        fragmentReplace(new HomeFragment(),"Home Fragment");
        txt_toolbar_title.setText("Your Fragment");
    }
  else{
     super.onBackPressed();
   }
 }


public void fragmentReplace(Fragment fragment, String fragment_name)
{
    try
    {
        fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
        fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
        fragmentTransaction.addToBackStack(fragment_name);
        fragmentTransaction.commitAllowingStateLoss();
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}
0
Hardik Vasani

Mein Problem war ähnlich, aber ich überwand mich, ohne das Fragment am Leben zu erhalten. Angenommen, Sie haben eine Aktivität mit zwei Fragmenten - F1 und F2. F1 wird anfangs gestartet und enthält beispielsweise einige Benutzerinformationen. Unter bestimmten Umständen wird F2 angezeigt, wenn der Benutzer aufgefordert wird, zusätzliches Attribut - seine Telefonnummer einzugeben. Als Nächstes möchten Sie, dass diese Telefonnummer wieder zu F1 zurückkehrt und die Anmeldung abgeschlossen wird, aber Sie stellen fest, dass alle vorherigen Benutzerinformationen verloren gehen und Sie nicht über die vorherigen Daten verfügen. Das Fragment wird von Grund auf neu erstellt, und selbst wenn Sie diese Informationen in onSaveInstanceState gespeichert haben, wird das Bundle in onActivityCreated als null zurückgegeben.

Lösung: Speichern Sie die erforderlichen Informationen als Instanzvariable in der aufrufenden Aktivität. Übergeben Sie dann diese Instanzvariable in Ihr Fragment.

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    Bundle args = getArguments();

    // this will be null the first time F1 is created. 
    // it will be populated once you replace fragment and provide bundle data
    if (args != null) {
        if (args.get("your_info") != null) {
            // do what you want with restored information
        }
    }
}

Also folge ich meinem Beispiel: Bevor ich F2 anzeige, speichere ich Benutzerdaten in der Instanzvariablen mit einem Callback. Dann starte ich F2, der Benutzer gibt die Telefonnummer ein und drückt Speichern. Ich benutze einen anderen Rückruf in der Aktivität, sammle diese Informationen und ersetze mein Fragment F1, diesmal hat bündle Daten, die ich verwenden kann.

@Override
public void onPhoneAdded(String phone) {
        //replace fragment
        F1 f1 = new F1 ();
        Bundle args = new Bundle();
        yourInfo.setPhone(phone);
        args.putSerializable("you_info", yourInfo);
        f1.setArguments(args);

        getFragmentManager().beginTransaction()
                .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();

    }
}

Weitere Informationen zu Rückrufen finden Sie hier: https://developer.Android.com/training/basics/fragments/communicating.html

0
Dodi
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
        FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
        mTransaction.replace(id, mFragment);
        hideKeyboard();
        if (addToStack) {
            mTransaction.addToBackStack(tag);
        }
        mTransaction.commitAllowingStateLoss();
    }
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
0
Hitesh Manwani

Ersetzen Sie ein Fragment mit folgendem Code:

Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
                .addToBackStack("Tag_AddPayment")
                .commit();

Die Aktivität von onBackPressed () ist:

  @Override
public void onBackPressed() {
    Android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
    if (fm.getBackStackEntryCount() > 1) {

        fm.popBackStack();
    } else {


        finish();

    }
    Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());

}
0
Pratibha Sarode

Perfekte Lösung, bei der alte Fragmente im Stapel gefunden und geladen werden, wenn sie im Stapel vorhanden sind.

/**
     * replace or add fragment to the container
     *
     * @param fragment pass Android.support.v4.app.Fragment
     * @param bundle pass your extra bundle if any
     * @param popBackStack if true it will clear back stack
     * @param findInStack if true it will load old fragment if found
     */
    public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        String tag = fragment.getClass().getName();
        Fragment parentFragment;
        if (findInStack && fm.findFragmentByTag(tag) != null) {
            parentFragment = fm.findFragmentByTag(tag);
        } else {
            parentFragment = fragment;
        }
        // if user passes the @bundle in not null, then can be added to the fragment
        if (bundle != null)
            parentFragment.setArguments(bundle);
        else parentFragment.setArguments(null);
        // this is for the very first fragment not to be added into the back stack.
        if (popBackStack) {
            fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        } else {
            ft.addToBackStack(parentFragment.getClass().getName() + "");
        }
        ft.replace(R.id.contenedor_principal, parentFragment, tag);
        ft.commit();
        fm.executePendingTransactions();
    }

benutze es wie

Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true); 
0
Khemraj