Comment empêcher les vues personnalisées de perdre leur état à travers les changements d’orientation de l’écran

J’ai implémenté avec succès onRetainNonConfigurationInstance() pour mon Activity principale pour enregistrer et restaurer certains composants critiques sur les changements d’orientation de l’écran.

Mais il semble que mes vues personnalisées soient recréées à partir de zéro lorsque l’orientation change. Cela a du sens, bien que dans mon cas, cela ne soit pas pratique car la vue personnalisée en question est un tracé X / Y et les points tracés sont stockés dans la vue personnalisée.

Existe-t-il une façon astucieuse d’implémenter quelque chose de similaire à onRetainNonConfigurationInstance() pour une vue personnalisée, ou dois-je simplement implémenter des méthodes dans la vue personnalisée qui me permettent d’obtenir et de définir son “état”?

    Vous faites cela en implémentant View#onSaveInstanceState et View#onRestoreInstanceState et en étendant la classe View.BaseSavedState .

     public class CustomView extends View { private int stateToSave; ... @Override public Parcelable onSaveInstanceState() { //begin boilerplate code that allows parent classes to save state Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); //end ss.stateToSave = this.stateToSave; return ss; } @Override public void onRestoreInstanceState(Parcelable state) { //begin boilerplate code so parent classes can restore state if(!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState)state; super.onRestoreInstanceState(ss.getSuperState()); //end this.stateToSave = ss.stateToSave; } static class SavedState extends BaseSavedState { int stateToSave; SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); this.stateToSave = in.readInt(); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(this.stateToSave); } //required field that makes Parcelables from a Parcel public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; } } 

    Le travail est divisé entre la classe SavedState de View et de View. Vous devez faire tout le travail de lecture et d’écriture depuis et vers la Parcel dans la classe SavedState . Ensuite, votre classe View peut effectuer le travail d’extraction des membres d’état et effectuer le travail nécessaire pour ramener la classe à un état valide.

    Notes: View#onSavedInstanceState et View#onRestoreInstanceState sont appelés automatiquement pour vous si View#getId renvoie une valeur> = 0. Cela se produit lorsque vous lui atsortingbuez un identifiant dans XML ou appelez setId manuellement. Sinon, vous devez appeler View#onSaveInstanceState et écrire le Parcelable renvoyé dans la plot que vous avez Activity#onSaveInstanceState dans Activity#onSaveInstanceState pour enregistrer l’état et le lire ensuite et le transmettre à View#onRestoreInstanceState from Activity#onRestoreInstanceState .

    Un autre exemple simple de ceci est le CompoundButton

    Je pense que c’est une version beaucoup plus simple. Bundle est un type Parcelable qui implémente Parcelable

     public class CustomView extends View { private int stuff; // stuff @Override public Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putParcelable("superState", super.onSaveInstanceState()); bundle.putInt("stuff", this.stuff); // ... save stuff return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) // implicit null check { Bundle bundle = (Bundle) state; this.stuff = bundle.getInt("stuff"); // ... load stuff state = bundle.getParcelable("superState"); } super.onRestoreInstanceState(state); } } 

    Voici une autre variante qui utilise un mélange des deux méthodes ci-dessus. Combinant la rapidité et l’exactitude de Parcelable avec la simplicité d’un ensemble:

     @Override public Parcelable onSaveInstanceState() { Bundle bundle = new Bundle(); // The vars you want to save - in this instance a ssortingng and a boolean Ssortingng someSsortingng = "something"; boolean someBoolean = true; State state = new State(super.onSaveInstanceState(), someSsortingng, someBoolean); bundle.putParcelable(State.STATE, state); return bundle; } @Override public void onRestoreInstanceState(Parcelable state) { if (state instanceof Bundle) { Bundle bundle = (Bundle) state; State customViewState = (State) bundle.getParcelable(State.STATE); // The vars you saved - do whatever you want with them Ssortingng someSsortingng = customViewState.getText(); boolean someBoolean = customViewState.isSomethingShowing()); super.onRestoreInstanceState(customViewState.getSuperState()); return; } // Stops a bug with the wrong state being passed to the super super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE); } protected static class State extends BaseSavedState { protected static final Ssortingng STATE = "YourCustomView.STATE"; private final Ssortingng someText; private final boolean somethingShowing; public State(Parcelable superState, Ssortingng someText, boolean somethingShowing) { super(superState); this.someText = someText; this.somethingShowing = somethingShowing; } public Ssortingng getText(){ return this.someText; } public boolean isSomethingShowing(){ return this.somethingShowing; } } 

    Les réponses sont déjà excellentes, mais ne fonctionnent pas nécessairement pour les groupes de vues personnalisés. Pour que toutes les vues personnalisées conservent leur état, vous devez remplacer onSaveInstanceState() et onRestoreInstanceState(Parcelable state) dans chaque classe. Vous devez également vous assurer qu’ils ont tous des identifiants uniques, qu’ils soient gonflés à partir de XML ou ajoutés par programmation.

    Ce que j’ai trouvé était remarquablement similaire à la réponse de Kobor42, mais l’erreur est restée parce que j’ajoutais les vues à un ViewGroup personnalisé par programmation et n’atsortingbuais pas d’identifiants uniques.

    Le lien partagé par mato fonctionnera, mais cela signifie qu’aucune des vues individuelles ne gère son propre état – l’état entier est enregistré dans les méthodes ViewGroup.

    Le problème est que lorsque plusieurs de ces groupes ViewGroup sont ajoutés à une mise en page, les identifiants de leurs éléments provenant du fichier XML ne sont plus uniques (s’ils sont définis au format XML). Au moment de l’exécution, vous pouvez appeler la méthode statique View.generateViewId() pour obtenir un identifiant unique pour une vue. Ceci est uniquement disponible à partir de l’API 17.

    Voici mon code du ViewGroup (il est abstrait et mOriginalValue est une variable de type):

     public abstract class DetailRow extends LinearLayout { private static final Ssortingng SUPER_INSTANCE_STATE = "saved_instance_state_parcelable"; private static final Ssortingng STATE_VIEW_IDS = "state_view_ids"; private static final Ssortingng STATE_ORIGINAL_VALUE = "state_original_value"; private E mOriginalValue; private int[] mViewIds; // ... @Override protected Parcelable onSaveInstanceState() { // Create a bundle to put super parcelable in Bundle bundle = new Bundle(); bundle.putParcelable(SUPER_INSTANCE_STATE, super.onSaveInstanceState()); // Use abstract method to put mOriginalValue in the bundle; putValueInTheBundle(mOriginalValue, bundle, STATE_ORIGINAL_VALUE); // Store mViewIds in the bundle - initialize if necessary. if (mViewIds == null) { // We need as many ids as child views mViewIds = new int[getChildCount()]; for (int i = 0; i < mViewIds.length; i++) { // generate a unique id for each view mViewIds[i] = View.generateViewId(); // assign the id to the view at the same index getChildAt(i).setId(mViewIds[i]); } } bundle.putIntArray(STATE_VIEW_IDS, mViewIds); // return the bundle return bundle; } @Override protected void onRestoreInstanceState(Parcelable state) { // We know state is a Bundle: Bundle bundle = (Bundle) state; // Get mViewIds out of the bundle mViewIds = bundle.getIntArray(STATE_VIEW_IDS); // For each id, assign to the view of same index if (mViewIds != null) { for (int i = 0; i < mViewIds.length; i++) { getChildAt(i).setId(mViewIds[i]); } } // Get mOriginalValue out of the bundle mOriginalValue = getValueBackOutOfTheBundle(bundle, STATE_ORIGINAL_VALUE); // get super parcelable back out of the bundle and pass it to // super.onRestoreInstanceState(Parcelable) state = bundle.getParcelable(SUPER_INSTANCE_STATE); super.onRestoreInstanceState(state); } } 

    Pour augmenter les autres réponses – si vous avez plusieurs vues composées personnalisées avec le même ID et qu’elles sont toutes restaurées avec l’état de la dernière vue lors d’un changement de configuration, il vous suffit d’indiquer à la répartition des événements de sauvegarde / restauration uniquement à lui-même en remplaçant quelques méthodes.

     class MyCompoundView : ViewGroup { ... override fun dispatchSaveInstanceState(container: SparseArray) { dispatchFreezeSelfOnly(container) } override fun dispatchRestoreInstanceState(container: SparseArray) { dispatchThawSelfOnly(container) } } 

    Pour une explication de ce qui se passe et pourquoi cela fonctionne, voir cet article de blog . Fondamentalement, les ID de vue des enfants de votre vue composée sont partagés par chaque vue composée et la restauration d’état devient confuse. En ne répartissant que l’état de la vue composée, nous empêchons leurs enfants de recevoir des messages mixtes provenant d’autres vues composées.