Fragment android - Comment enregistrer les états des vues dans un fragment lorsqu'un autre fragment est poussé dessus


137

Dans Android, un fragment (par exemple FragA) est ajouté à la backstack et un autre fragment (par exemple FragB) arrive en haut. Maintenant, en frappant en arrière FragAvient au sommet et le onCreateView()est appelé. Maintenant, j'avais FragAdans un état particulier avant d' FragBêtre poussé dessus.

Ma question est de savoir comment puis-je restaurer FragAson état antérieur? Existe-t-il un moyen de sauvegarder l'état (comme par exemple dans un bundle) et si oui, quelle méthode dois-je remplacer?

Réponses:


98

Dans l' exemple de FragmentList du guide de fragment, vous pouvez trouver:

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

Que vous pouvez utiliser plus tard comme ceci:

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

Je suis un débutant dans Fragments mais cela semble être une solution à votre problème;) OnActivityCreated est invoqué après le retour du fragment de la pile arrière.


18
Je ne pouvais pas faire fonctionner cela. SaveInstanceState était toujours nul. J'ajoute un fragment via une mise en page xml. J'ai dû changer le mCurCheckPosition en statique, puis cela fonctionne, mais se sent piraté.
scottyab

57
il n'appelle pas onSaveInstanceState - pourquoi le ferait-il? Donc, cette approche ne fonctionne pas.
minuit

10
Cette approche fonctionnera-t-elle vraiment au cas où nous voudrions conserver l'état de fragment tout en revenant d'un autre fragment dans la même activité? onSaveInstanceState () est appelé uniquement sur les événements Activity onPause / onStop. Selon les documentations: "Tout comme une activité, vous pouvez conserver l'état d'un fragment à l'aide d'un Bundle, au cas où le processus de l'activité serait tué et que vous deviez restaurer l'état du fragment lorsque l'activité est recréée. Vous pouvez enregistrer l'état pendant le rappel onSaveInstanceState () du fragment et le restaurer pendant soit onCreate (), onCreateView () ou onActivityCreated (). "
Paramvir Singh

26
Pour mémoire, cette approche est erronée et ne devrait pas connaître les votes positifs dont elle dispose. onSaveInstanceStaten'est appelée que lorsque son activité correspondante s'arrête également.
Martin Konecny

16
onSaveInstanceState () est appelé uniquement lorsque des modifications de configuration se sont produites et que l'activité est détruite, cette réponse est fausse
Tadas Valaitis

83

Les fragments ne onSaveInstanceState(Bundle outState)seront jamais appelés à moins que l'activité du fragment ne l'appelle sur lui-même et les fragments attachés. Ainsi, cette méthode ne sera pas appelée jusqu'à ce que quelque chose (généralement une rotation) force l'activité SaveInstanceStateet la restaure plus tard. Mais si vous n'avez qu'une seule activité et un grand ensemble de fragments à l'intérieur (avec une utilisation intensive de replace) et que l'application ne s'exécute que dans une seule activité d'orientation, elle onSaveInstanceState(Bundle outState)peut ne pas être appelée pendant longtemps.

Je connais trois solutions de contournement possibles.

La première:

utilisez les arguments de fragment pour contenir des données importantes:

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

La seconde, mais de manière moins pédant - variables de maintien dans singletons

Le troisième - ne pas replace()fragmenter mais add()/ show()/ hide()eux à la place.


3
La meilleure solution quand Fragment.onSaveInstanceState()on n'a jamais été appelé. Enregistrez simplement vos propres données dans l'argument, y compris les éléments de la vue liste ou simplement leurs identifiants (si vous avez un autre gestionnaire de données centralisé). Pas besoin d'enregistrer la position de la vue de liste - qui a été enregistrée et restaurée automatiquement.
John Pang

J'ai essayé d'utiliser votre exemple dans mon application, mais ceci: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);est toujours null. Quel est le problème?
vendredi

Utiliser les fragments getArguments()est DEFINITIVEMENT la voie à suivre, y compris les fragments imbriqués dans un ViewPager. J'utilise 1 activité et échangez de nombreux fragments in / out, et cela fonctionne parfaitement. Voici un test simple pour vous permettre de vérifier toute solution proposée: 1) passer du fragment A au fragment B; 2) changer l'orientation de l'appareil deux fois; 3) appuyez sur le bouton de retour de l'appareil.
Andy H.

J'ai essayé la première approche mais cela n'a pas fonctionné pour moi. getArguments () renvoie toujours null. Cela a également du sens car le fragment est remplacé et dans onCreate () vous définissez un nouveau Bundle afin que l'ancien Bundle soit perdu. Qu'est-ce que je manque et ai-je tort?
Zvi

@Zvi, j'ai écrit ce code il y a 1,5 ans et je ne me souviens pas de tous les détails, mais si je me souviens bien, replace ne recrée pas les fragments, fragment recréé uniquement si vous avez créé une nouvelle instance à partir de votre code. Dans ce cas, évidemment, le constructeur a appelé et setArguments(new Bundle());écrasé l'ancien Bundle. Assurez-vous donc d'avoir créé le fragment une seule fois, puis utilisez cette instance au lieu d'en créer une à chaque fois.
Fyodor Volchyok

20

Notez simplement que si vous travaillez avec des fragments à l'aide de ViewPager, c'est assez facile. Il vous suffit d'appeler cette méthode: setOffscreenPageLimit().

Accordign aux docs:

Définissez le nombre de pages à conserver de chaque côté de la page actuelle dans la hiérarchie d'affichage à l'état inactif. Les pages au-delà de cette limite seront recréées à partir de l'adaptateur si nécessaire.

Problème similaire ici


5
Ceci est différent. setOffScreenPageLimit agit comme un cache (c'est-à-dire combien de pages le ViewPager doit gérer à un moment donné) mais n'est pas utilisé pour sauvegarder l'état d'un fragment.
Renaud Mathieu

Dans mon cas, cela fonctionnait avec setOffscreenPageLimit () - bien que les fragments aient été détruits, l'état de la vue a été enregistré et restauré.
Davincho

Merci, m'a aidé aussi.
Elijah

Des années plus tard et cela est toujours aussi pertinent. Bien que cela ne réponde pas vraiment à la question, cela résout un problème
Supreme Dolphin

19

Gonflez simplement votre View pour une fois.

suit Exemple:

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

}


devrait également conserver les fragments existants dans un tableau ou quelque chose comme ça
Amir

J'ai entendu dire que garder une référence à rootView d'un fragment est une mauvaise pratique - pourrait entraîner des fuites [citation nécessaire]?
giraffe.guru

1
@ giraffe.guru se Fragmentréfère à sa vue racine ne fera pas cela. Alors que la référence par certains éléments GC-root sera, comme la propriété statique globale, une variable de thread non-ui. Une Fragmentinstance n'est pas GC-root, elle peut donc être récupérée. Il en sera de même pour sa vue racine.
Lym Zoy

Vous même ma journée.
Vijendra patidar le

Doux et simple.
Rupam Das le

8

J'ai travaillé avec un problème très similaire à celui-ci. Comme je savais que je reviendrais fréquemment à un fragment précédent, j'ai vérifié si le fragment .isAdded()était vrai, et si oui, plutôt que de faire un, transaction.replace()je fais juste un transaction.show(). Cela empêche le fragment d'être recréé s'il est déjà sur la pile - aucune sauvegarde d'état n'est nécessaire.

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

Une autre chose à garder à l'esprit est que, bien que cela préserve l'ordre naturel des fragments eux-mêmes, vous devrez peut-être gérer l'activité elle-même détruite et recréée lors du changement d'orientation (de configuration). Pour contourner ce problème dans AndroidManifest.xml pour votre nœud:

android:configChanges="orientation|screenSize"

Dans Android 3.0 et supérieur, le screenSizeest apparemment requis.

Bonne chance


transaction.addToBackStack (button_id + "stack_item"); // que fait cette ligne. Qu'est-ce que button_id ici?
raghu_3

button_id est juste une variable composée. L'argument de chaîne passé à addToBackStack n'est qu'un nom facultatif pour l'état de la backstack - vous pouvez le définir sur null si vous ne gérez qu'un seul backstack.
rmirabelle

, j'ai un problème similaire à celui-ci avec des fragments, pouvez-vous s'il vous plaît le regarder- stackoverflow.com/questions/22468977/…
raghu_3

1
N'ajoutez jamais android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternetsvotre manifeste. Apprenez plutôt à enregistrer et à restaurer l'état, par exemple à partir d'ici: speakerdeck.com/cyrilmottier
Marcin Koziński

Et puis une fois que vous avez appris à quel point l'état de sauvegarde est incroyablement lourd et que la solution présentée résout le problème le plus proprement dans votre situation particulière, allez-y et utilisez-la, sans vous soucier des
votes négatifs

5

La meilleure solution que j'ai trouvée est ci-dessous:

onSavedInstanceState (): toujours appelé à l'intérieur du fragment lorsque l'activité va s'arrêter (déplacer l'activité de l'une à l'autre ou modifier la configuration). Donc, si nous appelons plusieurs fragments sur la même activité, nous devons utiliser l'approche suivante:

Utilisez OnDestroyView () du fragment et enregistrez l'objet entier dans cette méthode. Then OnActivityCreated (): Vérifiez que si l'objet est nul ou non (car cette méthode appelle à chaque fois). Maintenant, restaurez l'état d'un objet ici.

Cela fonctionne toujours!


4

si vous gérez les changements de configuration dans votre activité de fragment spécifiée dans le manifeste Android comme ceci

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

alors le onSaveInstanceStatedu fragment ne sera pas invoqué et l' savedInstanceStateobjet sera toujours nul.


1

je ne pense pas que ce onSaveInstanceStatesoit une bonne solution. il ne sert qu'à une activité qui avait été déstockée.

Depuis Android 3.0, le Fragmen a été gestionnaire par FragmentManager, la condition est: une activité de cartographie de nombreux fragments, lorsque le fragment est ajouté (pas remplacé: il sera recréé) dans backStack, la vue sera déstockée. une fois de retour au dernier, il s'affichera comme avant.

Je pense donc que le fragmentManger et la transaction sont assez bons pour le gérer.


0

J'ai utilisé une approche hybride pour les fragments contenant une vue de liste. Cela semble être performant puisque je ne remplace pas le fragment actuel, mais ajoute plutôt le nouveau fragment et cache le fragment actuel. J'ai la méthode suivante dans l'activité qui héberge mes fragments:

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

J'utilise cette méthode dans mon fragment (contenant la vue de liste) chaque fois qu'un élément de liste est cliqué / tapé (et donc j'ai besoin de lancer / afficher le fragment de détails):

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()renvoie un tableau de chaînes que j'utilise comme balises pour différents fragments lorsque j'ajoute un nouveau fragment (voir transaction.addméthode dans la addFragmentméthode ci-dessus).

Dans le fragment contenant la vue liste, je fais ceci dans sa méthode 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();
}

Puis dans onCreateView du fragment (en fait dans une méthode qui est invoquée dans onCreateView), je restaure l'état:

// 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

En fin de compte, après avoir essayé plusieurs de ces solutions compliquées, car je n'avais besoin que de sauvegarder / restaurer une seule valeur dans mon Fragment (le contenu d'un EditText), et bien que ce ne soit peut-être pas la solution la plus élégante, créer une SharedPreference et stocker mon état là a travaillé pour moi


0

Un moyen simple de conserver les valeurs des champs dans différents fragments d'une activité

Créer les instances de fragments et ajouter au lieu de remplacer et supprimer

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

Ensuite, affichez et masquez simplement les fragments au lieu de les ajouter et de les supprimer à nouveau

    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
Veuillez envisager d'inclure des informations sur votre réponse, plutôt que de simplement publier un code. Nous essayons non seulement de fournir des «correctifs», mais d'aider les gens à apprendre. Vous devez expliquer ce qui n'allait pas dans le code d'origine, ce que vous avez fait différemment et pourquoi vos modifications ont fonctionné.
Andrew Barber
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.