studio rotación rotacion reinicie que pantalla orientacion movil manejando girar evitar detectar configchanges como cambio bloquear activity android android-activity state screen-orientation

android - rotación - Cómo evitar que las vistas personalizadas pierdan el estado en los cambios de orientación de la pantalla



detectar cambio de orientacion android (5)

He implementado con éxito onRetainNonConfigurationInstance() para mi Activity principal para guardar y restaurar ciertos componentes críticos en los cambios de orientación de la pantalla.

Pero parece que mis vistas personalizadas se están recreando desde cero cuando cambia la orientación. Esto tiene sentido, aunque en mi caso es inconveniente porque la vista personalizada en cuestión es un gráfico X / Y y los puntos trazados se almacenan en la vista personalizada.

¿Existe una manera onRetainNonConfigurationInstance() implementar algo similar a onRetainNonConfigurationInstance() para una vista personalizada, o necesito simplemente implementar métodos en la vista personalizada que me permitan obtener y establecer su "estado"?


Aquí hay otra variante que utiliza una mezcla de los dos métodos anteriores. Combinando la velocidad y la corrección de Parcelable con la simplicidad de 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; } }


Creo que esta es una versión mucho más simple. Bundle es un tipo incorporado que implementa 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); } }


Esto se hace implementando View#onSaveInstanceState y View#onRestoreInstanceState y extendiendo la clase View.BaseSavedState .

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

El trabajo se divide entre la vista y la clase SavedState de la vista. Debe hacer todo el trabajo de lectura y escritura hacia y desde la Parcel en la clase SavedState . Luego su clase de Vista puede hacer el trabajo de extraer los miembros del estado y hacer el trabajo necesario para que la clase vuelva a un estado válido.

Notas: View#onSavedInstanceState y View#onRestoreInstanceState se llaman automáticamente si View#getId devuelve un valor> = 0. Esto sucede cuando le setId un ID en xml o llama a setId manualmente. De lo contrario, debe llamar a View#onSaveInstanceState y escribir el Parcelable devuelto al paquete que obtiene en Activity#onSaveInstanceState para guardar el estado y luego leerlo y pasarlo a View#onRestoreInstanceState de Activity#onRestoreInstanceState .

Otro ejemplo simple de esto es el CompoundButton


Las respuestas aquí ya son excelentes, pero no necesariamente funcionan para grupos de vistas personalizados. Para que todas las Vistas personalizadas conserven su estado, debe anular onSaveInstanceState() y onRestoreInstanceState(Parcelable state) en cada clase. También debe asegurarse de que todos tengan identificadores únicos, ya sean inflados desde xml o agregados mediante programación.

Lo que se me ocurrió fue muy similar a la respuesta de Kobor42, pero el error se mantuvo porque estaba agregando las Vistas a un grupo de vistas personalizado programáticamente y no asignando identificadores únicos.

El enlace compartido por mato funcionará, pero significa que ninguna de las Vistas individuales gestiona su propio estado; todo el estado se guarda en los métodos de ViewGroup.

El problema es que cuando se agregan varios de estos ViewGroups a un diseño, los identificadores de sus elementos del xml ya no son únicos (si están definidos en xml). En tiempo de ejecución, puede llamar al método estático View.generateViewId() para obtener una ID única para una Vista. Esto solo está disponible desde la API 17.

Aquí está mi código de ViewGroup (es abstracto, y mOriginalValue es una variable de tipo):

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


Para aumentar otras respuestas: si tiene varias vistas compuestas personalizadas con el mismo ID y todas se están restaurando con el estado de la última vista en un cambio de configuración, todo lo que debe hacer es decirle a la vista que solo envíe eventos de guardar / restaurar a sí mismo por anular un par de métodos.

class MyCompoundView : ViewGroup { ... override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) { dispatchFreezeSelfOnly(container) } override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) { dispatchThawSelfOnly(container) } }

Para obtener una explicación de lo que está sucediendo y por qué funciona esto, consulte esta publicación del blog . Básicamente, las vistas de vista de los niños de la vista compuesta se comparten con cada vista compuesta y la restauración del estado se confunde. Al solo enviar el estado para la vista compuesta, evitamos que sus hijos reciban mensajes mixtos de otras vistas compuestas.