2011-02-08 4 views
8

Pregunta

¿Cómo se puede guardar, ver estado de la instancia del widget cuando, mediante el uso de diseños de widgets XML definidos, los componentes de los casos individuales de widgets todos tienen el mismo ID?estado de ahorro de piensos compuestos Vea los aparatos

Ejemplo

Tomemos, por ejemplo, el widget NumberPicker que se utiliza en el widget TimePicker (tenga en cuenta que NumberPicker no está expuesto a la SDK). Este es un widget simple con tres componentes que se inflan desde number_picker.xml: un botón de incremento, un botón de disminución y otro EditText donde puede ingresar un número directamente. Para que el código interactúe con estos widgets, todos tienen ID (R.id.increment, R.id.decrement y R.id.timepicker_input, respectivamente).

Digamos que usted tiene tres NumberPicker s en un diseño de XML y se les da identificadores distintos (por ejemplo. R.id.hour, R.id.minute) .¹ Esta disposición se infla a la vista del contenido de una actividad. Decidimos cambiar la orientación de la actividad, por lo que Activity.onSaveInstanceState(Bundle) guarda nuestro estado de vista para cada vista que tenga una ID (este es el comportamiento predeterminado).

Desafortunadamente, las tres NumberPicker s tienen s que comparten la misma ID - R.id.timepicker_input. Por lo tanto, cuando se restaura la actividad, la que está más abajo en la jerarquía de vista es aquella cuyo estado parece conservarse para los tres. Además, el foco va al primer NumberPicker cuando se restaura independientemente de cuál haya tenido foco cuando se guarde.

TimePicker soluciona este problema conservando el estado por separado. Desafortunadamente, esto no conservará la posición del cursor o la vista enfocada sin mucho más trabajo. No estoy seguro de cómo conserva ese estado si lo hace en absoluto (y jugar rápidamente con un cuadro de diálogo de entrada de tiempo parece indicar que puede hacerlo de alguna manera).

Por favor, vea el código de ejemplo para demostrar este problema: https://github.com/xxv/AndroidNumberPickerBug


¹ En la jerarquía de vistas, esto establece el ID de la LinearLayout que NumberPicker se extiende a sus documentos de identidad.

+0

amablemente allanan la pregunta. es un dolor leer esto ahora – Javanator

+1

@Javanator: Lo reescribí, con suerte esta vez más claramente. Nota para mí: no escriba preguntas complejas de SA a las 4AM. –

+0

Parece que hay un error en Android debido a este problema. El widget TimePicker no maneja esto correctamente: http://code.google.com/p/android/issues/detail?id=5483 –

Respuesta

14

me encontré con el mismo problema cuando se trata de crear mi vista compuesta propia. Al mirar el código fuente de Android creo que la forma correcta de implementar vistas compuestas es que la vista compuesta asuma la responsabilidad de guardar y restaurar el estado de instancia de sus elementos secundarios y evitar que las llamadas guarden y restauren el estado de la instancia. pasó a las vistas de niño. Esto funciona en torno a la cuestión de que las ID de las vistas secundarias no sean únicas cuando tiene más de una instancia de la misma vista compuesta en una actividad.

Esto puede sonar complicado pero es realmente bastante fácil y las API realmente prevén este escenario exacto. He escrito una publicación de blog here sobre cómo se hace esto, pero esencialmente en su vista compuesta debe implementar los siguientes 4 métodos, personalizando onSaveInstanceState() y onRestoreInstanceState() para cumplir con sus requisitos específicos.

@Override 
protected Parcelable onSaveInstanceState() { 
    Parcelable superState = super.onSaveInstanceState(); 
    return new SavedState(superState, numberPicker1.getValue(), numberPicker2.getValue(), numberPicker3.getValue()); 
} 

@Override 
protected void onRestoreInstanceState(Parcelable state) { 
    SavedState savedState = (SavedState) state; 
    super.onRestoreInstanceState(savedState.getSuperState()); 

    numberPicker1.setValue(savedState.getNumber1()); 
    numberPicker2.setValue(savedState.getNumber2()); 
    numberPicker3.setValue(savedState.getNumber3()); 
} 

@Override 
protected void dispatchSaveInstanceState(SparseArray container) { 
    // As we save our own instance state, ensure our children don't save 
    // and restore their state as well. 
    super.dispatchFreezeSelfOnly(container); 
} 

@Override 
protected void dispatchRestoreInstanceState(SparseArray container) { 
    /** See comment in {@link #dispatchSaveInstanceState(android.util.SparseArray)} */ 
    super.dispatchThawSelfOnly(container); 
} 

En cuanto al problema con NumberPicker/TimePicker, como se ha mencionado en otro comentario parece que hay un error con NumberPicker y TimePicker. Para solucionarlo, puede anular ambos e implementar la solución que he descrito.

+1

Esta debería ser la respuesta aceptada. Lo intenté y funciona como se anuncia. ¡Gracias! –

+0

Vale la pena leer @Charles Harley's [publicación sobre este tema en más detalles] (http://www.charlesharley.com/2012/programming/views-saving-instance-state-in-android/), está muy bien ¡explicado! – rekaszeru

-3

Bastante simple: no es así. Simplemente vaya y desactive la mierda de cambio de orientación en su archivo de manifiesto. El mecanismo de guardar estado de vista es intrínsecamente defectuoso, simplemente no pensaron en esto.

Si desea conservar su estado, no puede volver a utilizar una identificación en una sola actividad. Lo que esencialmente significa que no puede usar un solo diseño más de una vez, lo que hace que los widgets más complejos como TimePicker sean básicamente imposibles de hacer correctamente.

Puede hacer que los niños mantengan su estado anulando dispatchSaveInstanceState y hackándolo, pero no encontré la manera de mantener el enfoque, además de administrarlo usted también.

Creo que podrían solucionar esto haciendo que el alcance del estado sin romper la API, pero no contenga la respiración.

+0

Me gusta su idea acerca de establecer el estado. No parece demasiado difícil de agregar y admito, había asumido que estaría allí en primer lugar cuando comencé a jugar con widgets reutilizables. –

+0

Como nota adicional: aún no he trabajado con esto, pero es probable que la nueva Fragment API solucione problemas como este. Si cada fragmento realiza un seguimiento de su propio estado (como las Actividades lo hacen ahora), simplemente podría realizar widgets como Fragmentos y reutilizarlos a voluntad. Y lo mejor es que puedes usar la API en 2.x también. De verdad, realmente espero que funcione así, de lo contrario perderé mi fe en Android. –

+0

Aún no se trata de casos en los que desea tener dos widgets en la misma "forma" (por ejemplo, si usa dos selectores de fecha en lo que sería el mismo fragmento). Según lo que entiendo, los Fragmentos están destinados a ser una colección funcional de widgets, generalmente para un conjunto completo de interacciones. Si ese es el caso, no creo que usar Fragment API para lo que es esencialmente un widget sea una decisión acertada. –

0

Acabo de encontrar exactamente el mismo problema con una vista compuesta con tres NumberPickers.Encontré una solución en otro sitio que implicaba reasignar el Id del timepicker_input a un ID aleatorio único. Esto funciona, pero es complicado porque una vez que se elige una Id nueva, una vez que se haya elegido una nueva Id, esa Id debe persistir en los cambios de configuración , por lo que se necesita un código adicional para hacerlo.

Para mi aplicación, y probablemente en el 99% de los demás, funciona un enfoque mucho más simple (hack) . Me di cuenta de que el espacio de nombres para Ids tiene espacio para 65536 identificadores únicos (0x7f090000 a 0x7f09ffff), pero mi aplicación solo usa 20 o menos, y se asignan monótonamente a partir del de inicio. Además, el propio widget NumberPicker tiene un único identificador dentro del árbol (por ejemplo, como en la publicación original, R.id.hour, R.id.minute y R.id.second). Mi solución es reasignar el Id del widget EditText al identificador del widget NumberPicker más un desplazamiento.

Este es un cambio de una línea en el código NumberPicker. Simplemente complemento:

mText.setId(getId() + 1000); 

Después de la siguiente línea en NumberPicker.java:

mText = (EditText) findViewById(R.id.timepicker_input); 

El desplazamiento por supuesto puede ser ajustado en base a los requisitos de aplicación.

Imagino que el mismo enfoque funcionará para otros widgets compuestos.

Para el ejemplo anterior, este enfoque permite guardar y restaurar el estado de los widgets de EditText individuales , y la vista enfocada también.

0

Llego un poco tarde al juego, pero quería dar mi opinión.

Puede usar el android:tag para agrupar cada View. Por lo tanto, puedes 'recargar' cada estado.

Desde el Android Developer página web:

androide: Etiqueta

de Suministro una etiqueta para esta vista que contiene una cadena, que se recuperarán posteriormente con View.getTag() o buscado con View.findViewWithTag().

0

que tienen un compuesto Widget con una descripción de estado que consiste en un único entero mValue
Ahora anular los dos métodos siguientes, así:

@Override 
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { 
    if(getId() != NO_ID) { 
     Parcel p = Parcel.obtain(); 
     p.writeInt(mValue); 
     p.setDataPosition(0); 
     container.put(getId(), new MyParcelable(p)); 
    } 
} 

@Override 
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { 
    if(getId() != NO_ID) { 
     Parcelable p = container.get(getId()); 
     if(p != null && p instanceof MyParcelable) { 
      MyParcelable mp = (MyParcelable) p; 
      updateAllValues(mp.getValue()); 
     } 
    } 
} 

En dispatchSaveInstanceState(), estás serialización de su estado. Los estados más complejos requerirán estructuras de almacenamiento más complejas.
Asegúrese de setDataPosition() correctamente.
sólo estoy guardando el estado si el widget tiene un identificador asignado el que estoy usando como etiqueta de la parcela

En dispatchRestoreInstanceState(), estás desempaquetar su paquete.
Solo estoy realizando el desempaquetado si el widget tiene una identificación asignada y el contenedor contiene un paquete con una etiqueta que coincida con esta identificación.
El Parcelable también debe ser de la clase adecuada.
El desempaquetado será más complicado para widgets más complejos.

El último componente que se necesita es una subclase Parcelable como la siguiente:

static class MyParcelable implements Parcelable { 

    private int mValue; 

    private MyParcelable(Parcel in) { 
     mValue = in.readInt(); 
    } 

    public int describeContents() { 
     return 0; 
    } 

    public void writeToParcel(Parcel dest, int flags) { 
     dest.writeInt(mValue); 
    } 

    int getValue() { 
     return mValue; 
    } 

    public static final Parcelable.Creator<MyParcelable> CREATOR 
     = new Parcelable.Creator<MyParcelable>() { 

      public MyParcelable createFromParcel(Parcel source) { 
       return new MyParcelable(source); 
      } 

      public MyParcelable[] newArray(int size) { 
       return new MyParcelable[size]; 
      } 
    }; 

} 

Usando este marco es mucho más fácil que la manipulación de fragmentos y la gestión de configuración

Cuestiones relacionadas