Jak zapobiec utracie stanu niestandardowych widoków podczas zmiany orientacji ekranu

Udało mi się wdrożyć onRetainNonConfigurationInstance() dla mojego głównego Activity, aby zapisać i przywrócić niektóre krytyczne komponenty w przypadku zmian orientacji ekranu.

Ale wydaje się, że moje niestandardowe widoki są tworzone od nowa, gdy zmienia się orientacja. Ma to sens, chociaż w moim przypadku jest to niewygodne, ponieważ widok niestandardowy, o którym mowa, to Wykres X/Y, a wykreślone punkty są przechowywane w widoku niestandardowym.

Czy istnieje sprytny sposób na zaimplementowanie czegoś podobnego do onRetainNonConfigurationInstance() dla widoku niestandardowego, Czy muszę po prostu zaimplementować metody w widoku niestandardowym, które pozwalają mi uzyskać i ustawić jego "stan"?

Author: JJD, 2010-08-22

5 answers

Robisz to poprzez wdrożenie View#onSaveInstanceState oraz View#onRestoreInstanceState i rozszerzanie View.BaseSavedState klasy.

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

Praca jest podzielona między widok i SavedState klasy widoku. Powinieneś wykonywać całą pracę czytania i pisania do i z Parcel w klasie SavedState. Wtedy Twoja klasa View może wykonać pracę ekstrakcji członków stanu i wykonać pracę niezbędną do przywrócenia klasy do poprawnego stanu.

Uwagi: View#onSavedInstanceState i View#onRestoreInstanceState są wywoływane automatycznie dla Ciebie jeśli View#getId Zwraca wartość > = 0. Dzieje się tak, gdy nadasz mu identyfikator w xml lub wywołasz ręcznie setId. W przeciwnym razie musisz zadzwonić View#onSaveInstanceState i napisać Parcelable zwrócony do paczki, którą otrzymujesz Activity#onSaveInstanceState, aby zapisać stan, a następnie odczytać go i przekazać do View#onRestoreInstanceState z Activity#onRestoreInstanceState.

Innym prostym przykładem tego jest CompoundButton

 397
Author: Rich Schuler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 22:35:00

Myślę, że jest to znacznie prostsza wersja. Bundle jest typem wbudowanym, który implementuje 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);
  }
}
 419
Author: Kobor42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-15 17:25:55

Oto inny wariant, który wykorzystuje mieszankę dwóch powyższych metod. Połączenie szybkości i poprawności Parcelable z prostotą 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
Author: Blundell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-24 14:42:43

Odpowiedzi tutaj już są świetne, ale niekoniecznie działają dla niestandardowych grup widoków. Aby wszystkie widoki niestandardowe zachowywały swój stan, musisz nadpisać onSaveInstanceState() i onRestoreInstanceState(Parcelable state) w każdej klasie. Musisz również upewnić się, że wszystkie mają unikalne identyfikatory, niezależnie od tego, czy są pompowane z xml, czy dodawane programowo.

To, co wymyśliłem, było bardzo podobne do odpowiedzi Kobor42, ale błąd pozostał, ponieważ dodałem widoki do niestandardowej ViewGroup programowo i nie przypisywałem unikalnych dokumenty.

Łącze udostępnione przez mato będzie działać, ale oznacza to, że żaden z widoków nie zarządza własnym stanem - cały stan jest zapisywany w metodach ViewGroup.

Problem polega na tym, że gdy wiele z tych grup widoków jest dodawanych do układu, identyfikatory ich elementów z xml nie są już unikalne (jeśli są zdefiniowane w xml). W czasie wykonywania można wywołać statyczną metodę View.generateViewId(), aby uzyskać unikalny identyfikator widoku. Jest to dostępne tylko z API 17.

Oto Mój kod z ViewGroup (jest abstrakcyjna, a mOriginalValue jest zmienną typu):

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);
    } 
}
 7
Author: Fletcher Johns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-06 16:15:09

Aby rozszerzyć inne odpowiedzi-jeśli masz wiele niestandardowych widoków złożonych o tym samym ID i wszystkie są przywracane ze stanem ostatniego widoku po zmianie konfiguracji, wszystko co musisz zrobić, to powiedzieć, aby Widok wysyłał tylko zdarzenia Zapisz/Przywróć do siebie przez nadpisanie kilku metod.

class MyCompoundView : ViewGroup {

    ...

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

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

Aby dowiedzieć się, co się dzieje i dlaczego to działa, Zobacz ten post na blogu. Zasadniczo identyfikatory dziecięcych widoków widoku złożonego są współdzielone przez każdy widok złożony a odbudowa państwa się myli. Wysyłając tylko stan dla samego widoku złożonego, uniemożliwiamy dzieciom otrzymywanie mieszanych wiadomości z innych widoków złożonych.

 0
Author: Tom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-29 01:02:28