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.