frammento Android: come salvare gli stati delle viste in un frammento quando un altro frammento viene spinto sopra di esso


137

In Android, un frammento (diciamo FragA) viene aggiunto allo backstack e un altro frammento (diciamo FragB) arriva in cima. Ora il colpire torna FragAin cima e onCreateView()viene chiamato. Ora mi ero trovato FragAin uno stato particolare prima di FragBesserci spinto sopra.

La mia domanda è: come posso ripristinare il FragAsuo stato precedente? C'è un modo per salvare lo stato (come dire in un pacchetto) e, in tal caso, quale metodo dovrei ignorare?

Risposte:


98

Nell'esempio di frammento della guida ai frammenti puoi trovare:

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

Che puoi usare in seguito in questo modo:

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

Sono un principiante in Fragments ma sembra una soluzione al tuo problema;) OnActivityCreated viene invocato dopo che il frammento ritorna dallo stack posteriore.


18
Non riuscivo a far funzionare questo. L'istanza era sempre nulla. Sto aggiungendo frammento tramite layout XML. Ho dovuto cambiare mCurCheckPosition in statico, quindi funziona, ma sembra confuso.
scottyab,

57
non chiama onSaveInstanceState - perché dovrebbe? Quindi, questo approccio non funziona.
mezzanotte del

10
Questo approccio funzionerà davvero nel caso in cui vogliamo mantenere lo stato del frammento mentre ritorniamo da un altro frammento nella stessa attività? onSaveInstanceState () viene chiamato solo per gli eventi Activity onPause / onStop. Secondo le documentazioni: "Analogamente a un'attività, è possibile mantenere lo stato di un frammento utilizzando un pacchetto, nel caso in cui il processo dell'attività venga interrotto e sia necessario ripristinare lo stato del frammento quando l'attività viene ricreata. È possibile salvare lo stato durante il callback onSaveInstanceState () del frammento e ripristinarlo durante onCreate (), onCreateView () o onActivityCreated (). "
Paramvir Singh,

26
Per la cronaca questo approccio è sbagliato e non dovrebbe avere dove vicino ai voti positivi che ha. onSaveInstanceStateviene chiamato solo quando anche l'attività corrispondente viene chiusa.
Martin Konecny,

16
onSaveInstanceState () viene chiamato onle quando si verificano modifiche alla configurazione e l'attività viene distrutta, questa risposta è errata
Tadas Valaitis

83

Il frammento onSaveInstanceState(Bundle outState)non verrà mai chiamato a meno che l'attività del frammento non lo chiami su se stesso e sui frammenti associati. Pertanto, questo metodo non verrà chiamato fino a quando qualcosa (in genere rotazione) forza l'attività SaveInstanceStatee la ripristina in un secondo momento. Ma se hai solo un'attività e una grande serie di frammenti al suo interno (con un uso intensivo di replace) e le applicazioni eseguite solo in un'attività di orientamento onSaveInstanceState(Bundle outState)potrebbero non essere chiamate per molto tempo.

Conosco tre possibili soluzioni alternative.

Il primo:

usa gli argomenti del frammento per contenere dati importanti:

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

Il secondo modo, ma meno pedante: contenere le variabili in singoli

Il terzo - non replace()frammenti ma add()/ show()/ hide()li invece.


3
La migliore soluzione quando Fragment.onSaveInstanceState()non è mai stata chiamata. Basta salvare i propri dati nell'argomento, inclusi gli elementi nella vista elenco o solo i loro ID (se si dispone di un altro gestore dati centralizzato). Non è necessario salvare la posizione della visualizzazione elenco, che è stata salvata e ripristinata automaticamente.
John Pang,

Ho provato a usare il tuo esempio nella mia app, ma questo: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);è sempre null. Qual è il problema?
venerdì

Utilizzare il frammento getArguments()è Sicuramente la strada da percorrere, compresi i frammenti nidificati in un ViewPager. Uso 1 attività e scambio molti frammenti in / out, e questo funziona perfettamente. Ecco un semplice test per controllare qualsiasi soluzione proposta: 1) passare dal frammento A al frammento B; 2) cambia due volte l'orientamento del dispositivo; 3) premere il pulsante Indietro sul dispositivo.
Andy H.

Ho provato il primo approccio ma non ha funzionato per me. getArguments () restituisce sempre null. Ha senso anche perché il frammento viene sostituito e in onCreate () si imposta un nuovo pacchetto in modo da perdere il vecchio pacchetto. Cosa mi manca e mi sbagliavo?
Zvi,

@Zvi, ho scritto questo codice 1,5 anni fa e non ricordo tutti i dettagli, ma come ricordo, sostituire non ricrea frammenti, frammento ricreato solo se hai creato una nuova istanza dal tuo codice. In questo caso, ovviamente, il costruttore ha chiamato e setArguments(new Bundle());sovrascrive il vecchio Bundle. Quindi assicurati di aver creato il frammento solo una volta, quindi utilizza questa istanza invece di crearne uno nuovo ogni volta.
Fyodor Volchyok,

20

Nota che se lavori con Fragments usando ViewPager, è abbastanza facile. Hai solo bisogno di chiamare questo metodo: setOffscreenPageLimit().

Accorda ai documenti:

Impostare il numero di pagine che devono essere conservate su entrambi i lati della pagina corrente nella gerarchia di visualizzazione in uno stato inattivo. Le pagine oltre questo limite verranno ricreate dall'adattatore quando necessario.

Problema simile qui


5
Questo è diverso. setOffScreenPageLimit si comporta come una cache (ovvero quante pagine il ViewPager deve gestire in un determinato momento) ma non viene utilizzato per salvare lo stato di un frammento.
Renaud Mathieu,

Nel mio caso ha funzionato con setOffscreenPageLimit () - anche se i frammenti sono stati distrutti, lo stato di visualizzazione è stato salvato e ripristinato.
Davincho,

Grazie, mi ha aiutato anche io.
Elia

Anni dopo e questo è ancora così rilevante. Anche se in realtà non risponde alla domanda, risolve un problema
Supreme Dolphin il

19

Gonfia la visualizzazione per una volta.

L'esempio segue:

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

}


inoltre dovrebbe mantenere frammenti esistenti nell'array o qualcosa del genere
Amir,

Ho sentito che mantenere un riferimento al rootView di un frammento è una cattiva pratica - potrebbe causare perdite [citazione necessaria]?
giraffe.guru,

1
@ giraffe.guru si Fragmentriferisce alla sua vista radice non lo farà. Mentre il riferimento di alcuni elementi GC-root sarà, come la proprietà statica globale, variabile thread non-ui. Un Fragmentesempio non è GC-radice, in modo che possa essere garbage collection. Così sarà la sua vista radice.
Lym Zoy,

Sei la mia stessa giornata.
Vijendra patidar,

Dolce e semplice.
Rupam Das

8

Ho lavorato con un problema molto simile a questo. Dato che sapevo che sarei tornato spesso a un frammento precedente, ho controllato per vedere se il frammento .isAdded()era vero, e in tal caso, piuttosto che fare un, transaction.replace()faccio solo un transaction.show(). Ciò evita che il frammento venga ricreato se è già nello stack - non è necessario salvare lo stato.

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

Un'altra cosa da tenere a mente è che mentre ciò preserva l'ordine naturale dei frammenti stessi, potrebbe essere comunque necessario gestire l'attività stessa che viene distrutta e ricreata al cambio di orientamento (configurazione). Per aggirare questo problema in AndroidManifest.xml per il tuo nodo:

android:configChanges="orientation|screenSize"

In Android 3.0 e versioni successive, screenSizeè apparentemente necessario.

In bocca al lupo


transaction.addToBackStack (button_id + "stack_item"); // cosa fa questa riga. Che cos'è button_id qui?
raghu_3,

button_id è solo una variabile inventata. L'argomento stringa passato a addToBackStack è solo un nome facoltativo per lo stato del backstack: è possibile impostarlo su null se si sta gestendo un solo backstack.
rmirabelle,

, sto avendo un problema simile a questo con frammenti, ti invitiamo a guardare in it- stackoverflow.com/questions/22468977/...
raghu_3

1
Non aggiungere mai il android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetstuo manifest. Impara invece come salvare e ripristinare lo stato, ad esempio da qui: speakerdeck.com/cyrilmottier/…
Marcin Koziński,

E poi, una volta appreso quanto sia follemente ingombrante lo stato salvifico e che la soluzione presentata risolva il problema in modo più pulito nella tua situazione particolare, vai avanti e
usalo

5

La migliore soluzione che ho trovato è di seguito:

onSavedInstanceState (): chiamato sempre all'interno del frammento quando l'attività sta per essere chiusa (sposta l'attività da una all'altra o configura le modifiche). Quindi se stiamo chiamando più frammenti sulla stessa attività, allora dobbiamo usare il seguente approccio:

Usa OnDestroyView () del frammento e salva l'intero oggetto all'interno di quel metodo. Quindi OnActivityCreated (): verifica che l'oggetto sia null o meno (poiché questo metodo chiama ogni volta). Ora ripristina lo stato di un oggetto qui.

Funziona sempre!


4

se stai gestendo i cambiamenti di configurazione nella tua attività di frammento specificata in Android manifest in questo modo

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

quindi il onSaveInstanceStateframmento non verrà invocato e l' savedInstanceStateoggetto sarà sempre nullo.


1

non credo onSaveInstanceStatesia una buona soluzione. usa solo per attività che erano state distrutte.

Da Android 3.0 il Fragmen è stato gestito da FragmentManager, la condizione è: un'attività che mappa i frammenti manny, quando il frammento viene aggiunto (non sostituito: verrà ricreato) in BackStack, la vista sarà destabilizzata. quando torna all'ultimo, verrà visualizzato come prima.

Quindi penso che il frammentanger e la transazione siano abbastanza buoni da gestirlo.


0

Ho usato un approccio ibrido per frammenti contenenti una vista elenco. Sembra essere performante poiché non sostituisco il frammento corrente, ma piuttosto aggiungo il nuovo frammento e nascondo quello attuale. Ho il seguente metodo nell'attività che ospita i miei frammenti:

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

Uso questo metodo nel mio frammento (contenente la vista elenco) ogni volta che un elemento dell'elenco viene cliccato / toccato (e quindi ho bisogno di avviare / visualizzare il frammento dei dettagli):

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()restituisce una matrice di stringhe che utilizzo come tag per frammenti diversi quando aggiungo un nuovo frammento (vedi transaction.addmetodo nel addFragmentmetodo sopra).

Nel frammento contenente la vista elenco, lo faccio nel suo metodo onPause ():

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

Quindi in onCreateView del frammento (in realtà in un metodo invocato in onCreateView), ripristino lo stato:

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

0

Alla fine, dopo aver provato molte di queste complicate soluzioni, avevo solo bisogno di salvare / ripristinare un singolo valore nel mio frammento (il contenuto di un EditText), e anche se potrebbe non essere la soluzione più elegante, creare un riferimento condiviso e memorizzare il mio stato lì ha funzionato per me


0

Un modo semplice per mantenere i valori dei campi in diversi frammenti in un'attività

Crea le Istanze di frammenti e aggiungi invece di sostituire e rimuovere

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

Quindi mostra e nascondi i frammenti invece di aggiungerli e rimuoverli di nuovo

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

;


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

5
Ti preghiamo di considerare di includere alcune informazioni sulla tua risposta, anziché semplicemente pubblicare il codice. Cerchiamo di fornire non solo "correzioni", ma aiutare le persone a imparare. Dovresti spiegare cosa c'era di sbagliato nel codice originale, cosa hai fatto diversamente e perché le tue modifiche hanno funzionato.
Andrew Barber,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.