Android-Fragment - So speichern Sie Ansichtszustände in einem Fragment, wenn ein anderes Fragment darauf verschoben wird

137

In Android wird ein Fragment (sagen wir FragA) zum Backstack hinzugefügt und ein anderes Fragment (sagen wir FragB) wird nach oben gebracht. Jetzt kommt beim Zurückschlagen FragAnach oben und das onCreateView()heißt. Jetzt war ich FragAin einem bestimmten Zustand, bevor ich darauf FragBgeschoben wurde.

Meine Frage ist, wie kann ich den FragAvorherigen Zustand wiederherstellen ? Gibt es eine Möglichkeit, den Status zu speichern (wie beispielsweise in einem Bundle), und wenn ja, welche Methode sollte ich überschreiben?

pankajagarwal
quelle

Antworten:

98

Im Fragment Guide FragmentList-Beispiel finden Sie:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Was Sie später so verwenden können:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Ich bin ein Anfänger in Fragmenten, aber es scheint eine Lösung für Ihr Problem zu sein;) OnActivityCreated wird aufgerufen, nachdem das Fragment vom Backstack zurückgekehrt ist.

ania
quelle
18
Ich konnte dies nicht zum Laufen bringen. SavedInstanceState war immer null. Ich füge Fragment über XML-Layout hinzu. Musste die mCurCheckPosition auf statisch ändern, dann funktioniert es, fühlt sich aber hackig an.
Scottyab
57
onSaveInstanceState wird nicht aufgerufen - warum sollte das so sein? Dieser Ansatz funktioniert also nicht.
Mitternacht
10
Funktioniert dieser Ansatz wirklich, wenn wir den Fragmentstatus beibehalten möchten, während wir von einem anderen Fragment in derselben Aktivität zurückkehren? onSaveInstanceState () wird nur bei Activity onPause / onStop-Ereignissen aufgerufen. In den Dokumentationen heißt es: "Ebenso wie bei einer Aktivität können Sie den Status eines Fragments mithilfe eines Bundles beibehalten, falls der Prozess der Aktivität abgebrochen wird und Sie den Fragmentstatus wiederherstellen müssen, wenn die Aktivität neu erstellt wird. Sie können den Status während speichern Der onSaveInstanceState () - Rückruf des Fragments und die Wiederherstellung während onCreate (), onCreateView () oder onActivityCreated (). "
Paramvir Singh
26
Für die Aufzeichnung ist dieser Ansatz falsch und sollte nicht annähernd die Up-Votes haben, die er hat. onSaveInstanceStatewird nur aufgerufen, wenn die entsprechende Aktivität ebenfalls heruntergefahren wird.
Martin Konecny
16
onSaveInstanceState () wird nur aufgerufen, wenn Konfigurationsänderungen aufgetreten sind und die Aktivität zerstört wird. Diese Antwort ist falsch
Tadas Valaitis
83

Fragmente onSaveInstanceState(Bundle outState)werden niemals aufgerufen, es sei denn, die Aktivität des Fragments nennt es sich selbst und angehängte Fragmente. Daher wird diese Methode erst aufgerufen, wenn etwas (normalerweise Rotation) die Aktivität SaveInstanceStateerzwingt und später wiederherstellt. Wenn Sie jedoch nur eine Aktivität und eine große Anzahl von Fragmenten darin haben (bei intensiver Nutzung von replace) und die Anwendung nur in einer Ausrichtung ausgeführt wird, werden Aktivitäten onSaveInstanceState(Bundle outState)möglicherweise nicht lange aufgerufen.

Ich kenne drei mögliche Problemumgehungen.

Der Erste:

Verwenden Sie die Argumente des Fragments, um wichtige Daten zu speichern:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

Der zweite, aber weniger pedantische Weg - Variablen in Singletons halten

Die dritte - nicht replace()Fragment aber add()/ show()/ hide()sie statt.

Fjodor Volchyok
quelle
3
Die beste Lösung, wenn Fragment.onSaveInstanceState()nie angerufen wurde. Speichern Sie einfach Ihre eigenen Daten im Argument, einschließlich der Elemente in der Listenansicht oder nur ihrer IDs (wenn Sie einen anderen zentralisierten Datenmanager haben). Die Position der Listenansicht muss nicht gespeichert werden - diese wurde gespeichert und automatisch wiederhergestellt.
John Pang
Ich habe versucht, Ihr Beispiel in meiner App zu verwenden, aber dies String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);ist immer so null. Was ist das Problem?
Fragon
Die Verwendung des Fragments getArguments()ist definitiv der richtige Weg, einschließlich verschachtelter Fragmente in einem ViewPager. Ich benutze 1 Aktivität und tausche viele Fragmente ein / aus, und das funktioniert perfekt. Hier ist ein einfacher Test, mit dem Sie jede vorgeschlagene Lösung überprüfen können: 1) Gehen Sie von Fragment A zu Fragment B; 2) zweimal die Ausrichtung des Geräts ändern; 3) Drücken Sie die Zurück-Taste am Gerät.
Andy H.
Ich habe den ersten Ansatz ausprobiert, aber er hat bei mir nicht funktioniert. getArguments () gibt immer null zurück. Dies ist auch deshalb sinnvoll, weil das Fragment ersetzt wird und Sie in onCreate () ein neues Bundle festlegen, damit das alte Bundle verloren geht. Was vermisse ich und irre ich mich?
Zvi
@Zvi, ich habe diesen Code vor 1,5 Jahren geschrieben und erinnere mich nicht an alle Details, aber wie ich mich erinnere, werden durch Ersetzen keine Fragmente neu erstellt. Fragmente werden nur neu erstellt, wenn Sie eine neue Instanz aus Ihrem Code erstellt haben. In diesem Fall hat der Konstruktor offensichtlich das setArguments(new Bundle());alte Bundle aufgerufen und überschrieben. Stellen Sie also sicher, dass Sie das Fragment nur einmal erstellt haben, und verwenden Sie diese Instanz, anstatt jedes Mal ein neues zu erstellen.
Fjodor Volchyok
20

Beachten Sie nur, dass es ziemlich einfach ist, mit Fragmenten mit ViewPager zu arbeiten. Sie müssen nur diese Methode aufrufen : setOffscreenPageLimit().

Entsprechen Sie den Dokumenten:

Legen Sie die Anzahl der Seiten fest, die im Leerlauf auf beiden Seiten der aktuellen Seite in der Ansichtshierarchie beibehalten werden sollen. Seiten, die dieses Limit überschreiten, werden bei Bedarf vom Adapter neu erstellt.

Ähnliches Problem hier

Vorabend
quelle
5
Das ist anders. setOffScreenPageLimit verhält sich wie ein Cache (dh wie viele Seiten der ViewPager zu einem bestimmten Zeitpunkt verarbeiten muss), wird jedoch nicht zum Speichern des Status eines Fragments verwendet.
Renaud Mathieu
In meinem Fall funktionierte es mit setOffscreenPageLimit () - obwohl die Fragmente zerstört wurden, wurde der Ansichtsstatus gespeichert und wiederhergestellt.
Davincho
Danke, hat mir auch geholfen.
Elijah
Jahre später und das ist immer noch so relevant. Obwohl es die Frage nicht wirklich beantwortet, löst es ein Problem
Supreme Dolphin
19

Blasen Sie Ihre Ansicht einfach einmal auf.

Beispiel folgt:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}}

Lym Zoy
quelle
sollte auch vorhandene Fragmente in Array oder so etwas halten
Amir
Ich habe gehört, dass es eine schlechte Praxis ist, einen Verweis auf die rootView eines Fragments zu behalten - könnte dies zu Undichtigkeiten führen [Zitieren erforderlich]?
Giraffe.guru
1
@ giraffe.guru Fragmentbezieht sich auf seine Root-Ansicht wird das nicht tun. Während die Referenzierung durch einige GC-Root-Elemente , wie die globale statische Eigenschaft, eine Nicht-UI-Thread-Variable ist. Eine FragmentInstanz ist keine GC-Wurzel, daher kann Müll gesammelt werden. Dies gilt auch für die Stammansicht.
Lym Zoy
Du bist mein Tag.
Vijendra Patidar
Süß und einfach.
Rupam Das
8

Ich habe mit einem ähnlichen Thema gearbeitet. Da ich wusste, dass ich häufig zu einem vorherigen Fragment zurückkehren würde, überprüfte ich, ob das Fragment .isAdded()wahr ist, und wenn ja, anstatt a zu tun, mache transaction.replace()ich einfach a transaction.show(). Dies verhindert, dass das Fragment neu erstellt wird, wenn es sich bereits auf dem Stapel befindet - es ist keine Speicherung des Status erforderlich.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Eine andere Sache, die Sie beachten sollten, ist, dass dies zwar die natürliche Reihenfolge der Fragmente selbst beibehält, Sie jedoch möglicherweise immer noch die Aktivität selbst behandeln müssen, die bei einer Änderung der Ausrichtung (Konfiguration) zerstört und neu erstellt wird. So umgehen Sie dies in AndroidManifest.xml für Ihren Knoten:

android:configChanges="orientation|screenSize"

In Android 3.0 und höher ist das screenSizeanscheinend erforderlich.

Viel Glück

rmirabelle
quelle
transaction.addToBackStack (button_id + "stack_item"); // Was macht diese Zeile. Was ist button_id hier?
Raghu_3
button_id ist nur eine zusammengesetzte Variable. Das an addToBackStack übergebene Zeichenfolgenargument ist nur ein optionaler Name für den Backstack-Status. Sie können ihn auf null setzen, wenn Sie nur einen einzelnen Backstack verwalten.
Rmirabelle
, ich habe ein ähnliches Problem mit Fragmenten, können Sie bitte in it- stackoverflow.com/questions/22468977/…
raghu_3
1
Fügen Sie niemals android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsin Ihr Manifest ein. Erfahren Sie
Marcin Koziński
Und wenn Sie dann erfahren haben, wie wahnsinnig unhandlich der Sparzustand ist und dass die vorgestellte Lösung das Problem in Ihrer speziellen Situation am saubersten löst, setzen Sie es ein, ohne sich um die Gegenstimmen derjenigen zu kümmern, die anderer Meinung sind ;-)
rmirabelle
5

Die beste Lösung, die ich gefunden habe, ist unten:

onSavedInstanceState (): Wird immer innerhalb des Fragments aufgerufen, wenn die Aktivität beendet wird (Aktivität von einer zur anderen verschieben oder Konfigurationsänderungen vornehmen). Wenn wir also mehrere Fragmente für dieselbe Aktivität aufrufen, müssen wir den folgenden Ansatz verwenden:

Verwenden Sie OnDestroyView () des Fragments und speichern Sie das gesamte Objekt in dieser Methode. Dann OnActivityCreated (): Überprüfen Sie, ob das Objekt null ist oder nicht (da diese Methode jedes Mal aufruft). Stellen Sie nun hier den Status eines Objekts wieder her.

Es funktioniert immer!

Aman Goel
quelle
4

Wenn Sie die Konfigurationsänderungen in Ihrer Fragmentaktivität behandeln, die im Android-Manifest wie folgt angegeben sind

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

dann wird das onSaveInstanceStatedes Fragments nicht aufgerufen und das savedInstanceStateObjekt ist immer null.

Brook Oldre
quelle
1

Ich denke nicht, dass onSaveInstanceStatees eine gute Lösung ist. es wird nur für Aktivitäten verwendet, die zerstört wurden.

Ab Android 3.0 wurde Fragmen von FragmentManager verwaltet. Die Bedingung lautet: Eine Aktivität, die Manny-Fragmente zuordnet. Wenn das Fragment in backStack hinzugefügt (nicht ersetzt: neu erstellt) wird, wird die Ansicht gespeichert. Wenn Sie zum letzten zurückkehren, wird es wie zuvor angezeigt.

Ich denke also, dass der fragmentManger und die Transaktion gut genug sind, um damit umzugehen.

kleiner dicker
quelle
0

Ich habe einen hybriden Ansatz für Fragmente verwendet, die eine Listenansicht enthalten. Es scheint performant zu sein, da ich das aktuelle Fragment nicht ersetze, sondern das neue Fragment hinzufüge und das aktuelle ausblende. Ich habe die folgende Methode in der Aktivität, die meine Fragmente hostet:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Ich verwende diese Methode in meinem Fragment (das die Listenansicht enthält), wenn auf ein Listenelement geklickt / getippt wird (und daher muss ich das Detailfragment starten / anzeigen):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags()Gibt ein Array von Zeichenfolgen zurück, die ich als Tags für verschiedene Fragmente verwende, wenn ich ein neues Fragment hinzufüge (siehe transaction.addMethode in addFragmentMethode oben).

In dem Fragment, das die Listenansicht enthält, mache ich dies in der onPause () -Methode:

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Dann stelle ich in onCreateView des Fragments (tatsächlich in einer Methode, die in onCreateView aufgerufen wird) den Status wieder her:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Javad Sadeqzadeh
quelle
0

Am Ende, nachdem ich viele dieser komplizierten Lösungen ausprobiert hatte, musste ich nur einen einzelnen Wert in meinem Fragment (den Inhalt eines EditText) speichern / wiederherstellen, und obwohl dies möglicherweise nicht die eleganteste Lösung ist, eine SharedPreference erstellen und meinen Status speichern dort hat für mich gearbeitet

VMMF
quelle
0

Eine einfache Möglichkeit, die Werte von Feldern in verschiedenen Fragmenten einer Aktivität zu speichern

Erstellen Sie die Instanzen von Fragmenten und fügen Sie sie hinzu, anstatt sie zu ersetzen und zu entfernen

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Zeigen Sie dann einfach die Fragmente an und verbergen Sie sie, anstatt sie erneut hinzuzufügen und zu entfernen

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;;

Sreejesh K Nair
quelle
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Sathish Kumar
quelle
5
Bitte geben Sie einige Informationen zu Ihrer Antwort an, anstatt nur den Code zu veröffentlichen. Wir versuchen, nicht nur "Korrekturen" bereitzustellen, sondern den Menschen beim Lernen zu helfen. Sie sollten erklären, was im ursprünglichen Code falsch war, was Sie anders gemacht haben und warum Ihre Änderung (en) funktioniert haben.
Andrew Barber