Comment éviter des vues personnalisées de perdre l'état à travers des changements d'orientation de l'écran

StackOverflow https://stackoverflow.com/questions/3542333

Question

Je l'ai mis en œuvre avec succès onRetainNonConfigurationInstance() pour ma principale Activity pour sauver et restaurer certains composants critiques à travers des changements d'orientation de l'écran.

Mais il semble, mes vues personnalisées sont recréés à partir de zéro lorsque les changements d'orientation. Cela est logique, bien que dans mon cas, il est peu pratique parce que la vue personnalisée en question est un tracé X / Y et les points tracés sont stockés dans la vue personnalisée.

Y at-il un moyen astucieux de mettre en œuvre quelque chose de similaire à onRetainNonConfigurationInstance() pour une vue personnalisée, ou dois-je simplement mettre en œuvre des méthodes dans la vue personnalisée qui me permettent d'obtenir et mis son « état »?

Était-ce utile?

La solution

Pour ce faire, la mise en œuvre View#onSaveInstanceState et View#onRestoreInstanceState et étendant le View.BaseSavedState classe.

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<SavedState> CREATOR =
        new Parcelable.Creator<SavedState>() {
          public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
          }
          public SavedState[] newArray(int size) {
            return new SavedState[size];
          }
    };
  }
}

Le travail est partagé entre la vue et la classe SavedState de la vue. Vous devez faire tout le travail de la lecture et l'écriture et de la Parcel dans la classe SavedState. Ensuite, votre classe View peut faire le travail d'extraction des membres de l'Etat et de faire le travail nécessaire pour obtenir le retour de classe à un état valide.

Notes: View#onSavedInstanceState et View#onRestoreInstanceState sont appelés automatiquement pour vous si View#getId retourne une valeur> = 0. Cela se produit lorsque vous lui donnez une carte d'identité en xml ou appelez setId manuellement. Sinon, vous devez appeler View#onSaveInstanceState et écrire le Parcelable retourné au colis que vous obtenez dans Activity#onSaveInstanceState pour enregistrer l'état et de lire la suite et le transmettre à View#onRestoreInstanceState de Activity#onRestoreInstanceState.

Un autre exemple simple est le CompoundButton

Autres conseils

Je pense que ceci est une version beaucoup plus simple. Bundle est un type intégré 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. La combinaison de la vitesse et l'exactitude des Parcelable avec la simplicité d'un Bundle:

@Override
public Parcelable onSaveInstanceState() {
    Bundle bundle = new Bundle();
    // The vars you want to save - in this instance a string and a boolean
    String someString = "something";
    boolean someBoolean = true;
    State state = new State(super.onSaveInstanceState(), someString, 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
        String someString = 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 String STATE = "YourCustomView.STATE";

    private final String someText;
    private final boolean somethingShowing;

    public State(Parcelable superState, String someText, boolean somethingShowing) {
        super(superState);
        this.someText = someText;
        this.somethingShowing = somethingShowing;
    }

    public String getText(){
        return this.someText;
    }

    public boolean isSomethingShowing(){
        return this.somethingShowing;
    }
}

Les réponses sont ici déjà très bien, mais ne fonctionnent pas nécessairement pour ViewGroups personnalisés. Pour obtenir toutes les vues personnalisées de conserver leur état, vous devez remplacer onSaveInstanceState() et onRestoreInstanceState(Parcelable state) dans chaque classe. Vous devez également vous assurer qu'ils ont tous ids uniques, qu'ils soient gonflés de xml ou ajouté un programme.

Qu'est-ce que je suis venu avec était remarquablement comme la réponse de Kobor42, mais l'erreur resté parce que j'ajoutais les vues à une ViewGroup personnalisée et non programmation attribuer des identifiants uniques.

Le lien partagé par mato fonctionnera, mais cela signifie qu'aucun des vues individuelles gérer leur propre état -. Tout l'état est enregistré dans les méthodes de ViewGroup

Le problème est que lorsque plusieurs de ces ViewGroups sont ajoutés à une mise en page, les ids de leurs éléments à partir du XML ne sont plus uniques (si son défini en XML). Lors de l'exécution, vous pouvez appeler la méthode View.generateViewId() statique pour obtenir un identifiant unique pour une vue. Ceci est uniquement disponible à partir de l'API 17.

Voici mon code de la ViewGroup (elle est abstraite et mOriginalValue est une variable de type):

public abstract class DetailRow<E> extends LinearLayout {

    private static final String SUPER_INSTANCE_STATE = "saved_instance_state_parcelable";
    private static final String STATE_VIEW_IDS = "state_view_ids";
    private static final String 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);
    } 
}

J'ai eu le problème onRestoreInstanceState restauré toutes mes vues personnalisées avec l'état de la dernière vue. Je l'ai résolu en ajoutant ces deux méthodes à mon point de vue personnalisé:

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    dispatchFreezeSelfOnly(container);
}

@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    dispatchThawSelfOnly(container);
}

Au lieu d'utiliser onSaveInstanceState et onRestoreInstanceState, vous pouvez également utiliser un ViewModel . Assurez-vous étendre votre modèle de données ViewModel, et vous pouvez utiliser ViewModelProviders pour obtenir à chaque fois la même instance de votre modèle l'activité est recréée:

class MyData extends ViewModel {
    // have all your properties with getters and setters here
}

public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // the first time, ViewModelProvider will create a new MyData
        // object. When the Activity is recreated (e.g. because the screen
        // is rotated), ViewModelProvider will give you the initial MyData
        // object back, without creating a new one, so all your property
        // values are retained from the previous view.
        myData = ViewModelProviders.of(this).get(MyData.class);

        ...
    }
}

Pour utiliser ViewModelProviders, ajoutez ce qui suit dans dependencies app/build.gradle:

implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:viewmodel:1.1.1"

Notez que votre MyActivity étend FragmentActivity au lieu d'étendre Activity.

Vous pouvez en savoir plus sur ViewModels ici:

Pour compléter d'autres réponses - si vous avez plusieurs vues composé personnalisé avec le même ID et ils sont tous en cours de restauration à l'état de la dernière vue sur un changement de configuration, tout ce que vous devez faire est de dire la vue seulement expédition sauver / restauration des événements à lui-même en remplaçant deux méthodes.

class MyCompoundView : ViewGroup {

    ...

    override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
        dispatchFreezeSelfOnly(container)
    }

    override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
        dispatchThawSelfOnly(container)
    }
}

Pour une explication de ce qui se passe et pourquoi cela fonctionne, voir ce billet de blog . Fondamentalement, votre ID de vue sur la vue des enfants de composés sont partagés par chaque vue composé et est perturbé la restauration de l'État. En seul état de répartition pour la vue composé lui-même, nous empêchons leurs enfants d'obtenir des messages contradictoires d'autres vues composés.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top