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"?
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
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);
}
}
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;
}
}
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);
}
}
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.
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