J'ai implémenté avec succès onRetainNonConfigurationInstance()
pour ma Activity
principale afin de sauvegarder et de restaurer certains composants critiques lors de modifications de l'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 un moyen astucieux 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"?
Pour ce faire, vous devez implémenter View#onSaveInstanceState
et View#onRestoreInstanceState
et étendre le View.BaseSavedState
class.
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 divisé entre les classes View et Saved's State. Vous devriez faire tout le travail de lecture et d’écriture sur la variable Parcel
dans la classe SavedState
. Ensuite, votre classe View peut extraire les membres de l'état et effectuer le travail nécessaire pour rétablir l'état valide de la classe.
Remarques: View#onSavedInstanceState
et View#onRestoreInstanceState
sont appelés automatiquement pour vous si View#getId
renvoie une valeur> = 0. Cela se produit lorsque vous lui attribuez un identifiant xml ou appelez setId
manuellement. Sinon, vous devez appeler View#onSaveInstanceState
et écrire le colis pouvant être renvoyé dans le colis que vous obtenez dans Activity#onSaveInstanceState
pour enregistrer l’état puis le lire et le transmettre au View#onRestoreInstanceState
à partir de Activity#onRestoreInstanceState
.
Un autre exemple simple est le CompoundButton
Je pense que c'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 . Combinant la rapidité et l'exactitude de 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 ici sont déjà excellentes, mais ne fonctionnent pas nécessairement pour les ViewGroups 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'elles ont toutes des identifiants uniques, qu'ils soient gonflés à partir de xml ou ajoutés par programme.
Ce que j’ai trouvé ressemblait remarquablement à la réponse de Kobor42, mais l’erreur persistait parce que j’ajoutais les vues à un ViewGroup personnalisé par programme et que je n’attribuais pas d’ID uniques.
Le lien partagé par mato fonctionnera, mais cela signifie qu'aucune des vues individuelles ne gérera son propre état - l'ensemble de l'état est enregistré dans les méthodes ViewGroup.
Le problème est que lorsque plusieurs de ces ViewGroups sont ajoutés à une mise en page, les identifiants de leurs éléments issus du XML ne sont plus uniques (s’il est défini dans 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 de ViewGroup (c'est abstrait, 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 qui 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 à ma vue personnalisée:
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
Pour compléter d'autres réponses - si vous avez plusieurs vues composées personnalisées avec le même ID et si 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 vue de ne distribuer que des événements de sauvegarde/restauration. à lui-même en surchargeant quelques 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 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 de l'état devient confuse. En envoyant uniquement l'état de la vue composée elle-même, nous empêchons leurs enfants de recevoir des messages contradictoires à partir d'autres vues composées.
Au lieu d'utiliser onSaveInstanceState
et onRestoreInstanceState
, vous pouvez également utiliser un ViewModel
. Faites que votre modèle de données étende ViewModel
, puis vous pourrez utiliser ViewModelProviders
pour obtenir la même instance de votre modèle chaque fois que 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 à dependencies
dans 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 de simplement prolonger Activity
.
Vous pouvez en savoir plus sur ViewModels ici: