it-swarm.com.de

So verhindern Sie, dass benutzerdefinierte Ansichten bei Änderungen der Bildschirmausrichtung den Status verlieren

Ich habe erfolgreich onRetainNonConfigurationInstance() für mein Haupt-Activity implementiert, um bestimmte kritische Komponenten bei Änderungen der Bildschirmausrichtung zu speichern und wiederherzustellen. 

Es scheint jedoch, dass meine benutzerdefinierten Ansichten von Grund auf neu erstellt werden, wenn sich die Ausrichtung ändert. Dies ist sinnvoll, obwohl dies in meinem Fall unpraktisch ist, da es sich bei der fraglichen benutzerdefinierten Ansicht um eine X/Y-Darstellung handelt und die gezeichneten Punkte in der benutzerdefinierten Ansicht gespeichert werden. 

Gibt es eine raffinierte Möglichkeit, etwas Ähnliches wie onRetainNonConfigurationInstance() für eine benutzerdefinierte Ansicht zu implementieren, oder muss ich nur Methoden in der benutzerdefinierten Ansicht implementieren, die es mir ermöglichen, seinen "Status" abzurufen und festzulegen?

231
Brad Hein

Sie tun dies, indem Sie View#onSaveInstanceState und View#onRestoreInstanceState implementieren und die View.BaseSavedState -Klasse erweitern.

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

Die Arbeit wird zwischen der View und der SavedState-Klasse der View aufgeteilt. Sie sollten alle Lese- und Schreibarbeiten für und von der Parcel in der SavedState-Klasse ausführen. Anschließend kann Ihre View-Klasse die Statusmitglieder extrahieren und die Arbeit ausführen, die erforderlich ist, um die Klasse wieder in einen gültigen Status zu bringen.

Hinweise: View#onSavedInstanceState und View#onRestoreInstanceState werden automatisch für Sie aufgerufen, wenn View#getId einen Wert> = 0 zurückgibt. Dies geschieht, wenn Sie ihm eine ID in xml geben oder setId manuell aufrufen. Andernfalls müssen Sie View#onSaveInstanceState anrufen und das Parcelable in das zurückgegebene Paket in Activity#onSaveInstanceState zurückschreiben, um den Status zu speichern und anschließend zu lesen und es von View#onRestoreInstanceState an Activity#onRestoreInstanceState zu übergeben.

Ein weiteres einfaches Beispiel ist die CompoundButton

401
Rich Schuler

Ich denke, das ist eine viel einfachere Version. Bundle ist ein integrierter Typ, der Parcelable implementiert.

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

Hier ist eine weitere Variante, die eine Mischung aus den beiden oben genannten Methoden verwendet: Kombination von Geschwindigkeit und Richtigkeit von Parcelable mit der Einfachheit einer 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;
    }
}
18
Blundell

Die Antworten hier sind bereits großartig, funktionieren jedoch nicht unbedingt für benutzerdefinierte ViewGroups. Damit alle benutzerdefinierten Ansichten ihren Status beibehalten können, müssen Sie onSaveInstanceState() und onRestoreInstanceState(Parcelable state) in jeder Klasse außer Kraft setzen. Sie müssen außerdem sicherstellen, dass alle eindeutigen IDs vorhanden sind.

Was mir dabei einfiel, war bemerkenswerterweise die Antwort von Kobor42, aber der Fehler blieb, weil ich die Ansichten programmgesteuert zu einer benutzerdefinierten ViewGroup hinzufügte und keine eindeutigen IDs zuweist.

Der von mato freigegebene Link funktioniert, aber es bedeutet, dass keine der einzelnen Ansichten ihren eigenen Status verwaltet. Der gesamte Status wird in den ViewGroup-Methoden gespeichert.

Das Problem ist, dass, wenn mehrere dieser ViewGroups zu einem Layout hinzugefügt werden, die IDs ihrer Elemente aus der XML-Datei nicht mehr eindeutig sind (wenn sie in XML definiert sind). Zur Laufzeit können Sie die statische Methode View.generateViewId() aufrufen, um eine eindeutige ID für eine View zu erhalten. Dies ist nur in API 17 verfügbar.

Hier ist mein Code aus der ViewGroup (es ist abstrakt und mOriginalValue ist eine Typvariable):

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);
    } 
}
8
Fletcher Johns

Ich hatte das Problem, dass onRestoreInstanceState alle meine benutzerdefinierten Ansichten mit dem Status der letzten Ansicht wiederhergestellt hat. Ich habe es gelöst, indem ich meiner benutzerdefinierten Ansicht diese beiden Methoden hinzugefügt habe:

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

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

Weitere Antworten - Wenn Sie über mehrere benutzerdefinierte zusammengesetzte Ansichten mit derselben ID verfügen und diese alle mit dem Status der letzten Ansicht bei einer Konfigurationsänderung wiederhergestellt werden, müssen Sie lediglich der Ansicht mitteilen, dass nur Speicher-/Wiederherstellungsereignisse ausgelöst werden sollen sich durch ein paar Methoden überschreiben.

class MyCompoundView : ViewGroup {

    ...

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

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

Für eine Erklärung, was passiert und warum das funktioniert, siehe diesen Blogbeitrag . Grundsätzlich werden die Ansichts-IDs für Kinder Ihrer zusammengesetzten Ansicht von jeder zusammengesetzten Ansicht geteilt, und die Wiederherstellung des Status wird verwirrt. Indem wir nur den Status für die zusammengesetzte Ansicht selbst versenden, verhindern wir, dass ihre Kinder gemischte Nachrichten von anderen zusammengesetzten Ansichten erhalten.

0
Tom

Anstelle von onSaveInstanceState und onRestoreInstanceState können Sie auch eine ViewModel verwenden. Nehmen Sie die Erweiterung Ihres Datenmodells um ViewModel vor. Anschließend können Sie ViewModelProviders verwenden, um jedes Mal die gleiche Instanz Ihres Modells abzurufen, wenn die Aktivität neu erstellt wird:

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

        ...
    }
}

Um ViewModelProviders zu verwenden, fügen Sie Folgendes in dependencies in app/build.gradle hinzu:

implementation "Android.Arch.lifecycle:extensions:1.1.1"
implementation "Android.Arch.lifecycle:viewmodel:1.1.1"

Beachten Sie, dass Ihre MyActivityFragmentActivity erweitert, anstatt nur Activity zu erweitern. 

Sie können hier mehr über ViewModels lesen:

0