2010-03-24 12 views
26

He encontrado una funcionalidad muy inesperada (e increíblemente frustrante) al intentar restaurar el estado de una lista de CheckBox es después de una rotación de pantalla. Pensé que primero trataría de dar una explicación textual sin el código, en caso de que alguien sea capaz de determinar una solución sin todos los detalles sangrientos. Si alguien necesita más detalles, puedo publicar el código.Android CheckBox - Restaurando el estado después de la rotación de la pantalla

Tengo una lista desplegable de View s compleja que contiene CheckBox es. No he podido restablecer el estado de estas casillas de verificación después de una rotación de pantalla. He implementado onSaveInstanceState y he transferido con éxito la lista de casillas de verificación seleccionadas al método onCreate. Esto se maneja pasando un long[] de identificadores de base de datos al Bundle.

En onCreate() compruebo el Bundle para la matriz de ids. Si la matriz está allí, la utilizo para determinar qué casillas de verificación verificar cuando se está construyendo la lista. He creado varios métodos de prueba y he confirmado que las casillas de verificación se están configurando correctamente, en función de la matriz de id. Como última comprobación, estoy verificando los estados de todas las casillas de verificación al final de onCreate(). Todo se ve bien ... a menos que gire la pantalla.

Cuando giro la pantalla, ocurre una de dos cosas: 1) Si se selecciona cualquier número de casillas de verificación, excepto la última, todas las casillas de verificación están desactivadas después de una rotación. 2) Si se marca la última casilla de verificación antes de la rotación, todas las casillas de verificación son marcadas después de la rotación.

Como dije, verifico el estado de las cajas al final de mi onCreate(). El caso es que el estado de los cuadros al final de onCreate es correcto según lo que seleccioné antes de la rotación. Sin embargo, el estado de los cuadros en la pantalla no refleja esto.

Además, me han puesto en práctica setOnCheckChangedListener() y me han confirmado que el estado de mis casillas de verificación están siendo alterados después mis onCreate devuelve el método de cada casilla de verificación.

¿Alguien tiene una idea de lo que está pasando? ¿Por qué cambiaría el estado de mis casillas de verificación después de que mi método onCreate regrese?

Gracias de antemano por su ayuda. He estado intentando degubar esto por un par de días. Cuando descubrí que mis casillas de verificación aparentemente cambiaban en algún lugar fuera de mi propio código, pensé que era hora de preguntar.

+0

Creo que se llama onResume después de onCreate cuando se realiza un cambio de orientación. ¿Está pasando algo en RePress? –

Respuesta

1

Rpond, no he anulado en Resumir, así que no creo que ese sea el problema. Aquí está la Actividad y los diseños asociados para que todos puedan ver. En el código verá muchas instrucciones Log.d (hubo incluso más en un punto). Con estas declaraciones Log pude verificar que el código funciona exactamente como lo esperaba. Además, observe el onCheckChangedListener que agrego a cada CheckBox. Todo lo que hace es imprimir una declaración de registro que me dice que el estado de uno de mis cuadros de control ha cambiado. Fue a través de esto que pude determinar el estado de los CheckBoxes después de que mi onCreate regresara. Verás cómo llamo examineCheckboxes() al final de mi onCreate. Las declaraciones de Log producidas a partir de esto no son lo que se muestra en mi pantalla después de una rotación, y puedo ver el estado de mis cajas después (debido a onCheckChangedListener)

Seleccionar ítems.java:

SelectItems clase pública se extiende Actividad {

public static final int SUCCESS = 95485839; 

public static final String ITEM_LIST = "item list"; 
public static final String ITEM_NAME = "item name"; 

// Save state constants 
private final String SAVE_SELECTED = "save selected"; 

private DbHelper db; 

private ArrayList<Long> selectedIDs; 

ArrayList<CheckBox> cboxes = new ArrayList<CheckBox>(); 

@Override 
public void onCreate(Bundle savedInstanceState) { 

    super.onCreate(savedInstanceState); 
    setContentView(R.layout.select_items); 

    // Create database helper 
    db = new DbHelper(this); 
    db.open(); 

    initViews(savedInstanceState); 

    examineCheckboxes(); 

} 

private void initViews(Bundle savedState) { 
    initButtons(); 

    initHeading(); 

    initList(savedState); 
} 

private void initButtons() { 

    // Setup event for done button 
    Button doneButton = (Button) findViewById(R.id.done_button); 
    doneButton.setOnClickListener(new OnClickListener() { 
     public void onClick(View v) { 
      done(); 
     } 
    }); 

    // Setup event for cancel button 
    Button cancelButton = (Button) findViewById(R.id.cancel_button); 
    cancelButton.setOnClickListener(new OnClickListener() { 
     public void onClick(View v) { 
      cancel(); 
     } 
    }); 
} 

private void initHeading() { 

    String itemName = getIntent().getExtras().getString(ITEM_NAME); 

    TextView headingField = (TextView) findViewById(R.id.heading_field); 

    if (itemName.equals("")) { 
     headingField.setText("No item name!"); 
    } else { 
     headingField.setText("Item name: " + itemName); 
    } 
} 

private void initList(Bundle savedState) { 

    // Init selected id list 
    if (savedState != null && savedState.containsKey(SAVE_SELECTED)) { 
     long[] array = savedState.getLongArray(SAVE_SELECTED); 
     selectedIDs = new ArrayList<Long>(); 

     for (long id : array) { 
      selectedIDs.add(id); 
     } 

     Log.d("initList", "restoring from saved state"); 
     logIDList(); 
    } else { 
     selectedIDs = (ArrayList<Long>) getIntent().getExtras().get(
       ITEM_LIST); 

     Log.d("initList", "using database values"); 
     logIDList(); 
    } 

    // Begin building item list 
    LayoutInflater li = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    LinearLayout scrollContent = (LinearLayout) findViewById(R.id.scroll_content); 

    Cursor cursor = db.getAllItems(); 
    startManagingCursor(cursor); 
    cursor.moveToFirst(); 

    // For each Item entry, create a list element 
    while (!cursor.isAfterLast()) { 

     View view = li.inflate(R.layout.item_element, null); 
     TextView name = (TextView) view.findViewById(R.id.item_name); 
     TextView id = (TextView) view.findViewById(R.id.item_id); 
     CheckBox cbox = (CheckBox) view.findViewById(R.id.checkbox); 

     name.setText(cursor.getString(cursor 
       .getColumnIndexOrThrow(DbHelper.COL_ITEM_NAME))); 

     final long itemID = cursor.getLong(cursor 
       .getColumnIndexOrThrow(DbHelper.COL_ID)); 
     id.setText(String.valueOf(itemID)); 

     // Set check box states based on selectedIDs array 
     if (selectedIDs.contains(itemID)) { 
      Log.d("set check state", "setting check to true for " + itemID); 
      cbox.setChecked(true); 
     } else { 
      Log.d("set check state", "setting check to false for " + itemID); 
      cbox.setChecked(false); 
     } 

     cbox.setOnClickListener(new OnClickListener() { 

      public void onClick(View v) { 
       Log.d("onClick", "id: " + itemID + ". button ref: " 
         + ((CheckBox) v)); 
       checkChanged(itemID); 
      } 

     }); 

     //I implemented this listener just so I could see when my 
     //CheckBoxes were changing. Through this I was able to determine 
     //that my CheckBoxes were being altered outside my own code. 
     cbox.setOnCheckedChangeListener(new OnCheckedChangeListener() { 

      public void onCheckedChanged(CompoundButton arg0, boolean arg1) { 

       Log.d("check changed", "button: " + arg0 + " changed to: " 
         + arg1); 
      } 

     }); 


     cboxes.add(cbox); 

     scrollContent.addView(view); 

     cursor.moveToNext(); 
    } 

    cursor.close(); 

    examineCheckboxes(); 
} 

private void done() { 
    Intent i = new Intent(); 
    i.putExtra(ITEM_LIST, selectedIDs); 
    setResult(SUCCESS, i); 
    this.finish(); 
} 

private void cancel() { 
    db.close(); 
    finish(); 
} 

private void checkChanged(long itemID) { 
    Log.d("checkChaged", "checkChanged for: "+itemID); 

    if (selectedIDs.contains(itemID)) { 
     selectedIDs.remove(itemID); 
    } else { 
     selectedIDs.add(itemID); 
    } 
} 

@Override 
protected void onSaveInstanceState(Bundle outState) { 
    super.onSaveInstanceState(outState); 

    long[] array = new long[selectedIDs.size()]; 
    for (int i = 0; i < array.length; i++) { 
     array[i] = selectedIDs.get(i); 
    } 

    outState.putLongArray(SAVE_SELECTED, array); 
} 

//Debugging method used to see what is in selectedIDs at any point in time. 
private void logIDList() { 
    String list = ""; 

    for (long id : selectedIDs) { 
     list += id + " "; 
    } 

    Log.d("ID List", list); 
} 

//Debugging method used to check the state of all CheckBoxes. 
private void examineCheckboxes(){ 
    for(CheckBox cbox : cboxes){ 
     Log.d("Check Cbox", "obj: "+cbox+" checked: "+cbox.isChecked()); 
    } 
} 

}

select_items.xml:

<?xml version="1.0" encoding="utf-8"?> 

<TextView android:id="@+id/heading_field" 
    android:layout_width="fill_parent" android:layout_height="wrap_content" 
    android:layout_marginBottom="10dip" android:textSize="18sp" 
    android:textStyle="bold" /> 

<LinearLayout android:id="@+id/button_layout" 
    android:orientation="horizontal" android:layout_width="fill_parent" 
    android:layout_height="wrap_content" android:layout_alignParentBottom="true"> 

    <Button android:id="@+id/done_button" android:layout_weight="1" 
     android:layout_width="wrap_content" android:layout_height="wrap_content" 
     android:text="Done" /> 

    <Button android:id="@+id/cancel_button" android:layout_weight="1" 
     android:layout_width="wrap_content" android:layout_height="wrap_content" 
     android:text="Cancel" /> 

</LinearLayout> 

<ScrollView android:orientation="vertical" 
    android:layout_below="@id/heading_field" android:layout_above="@id/button_layout" 
    android:layout_width="fill_parent" android:layout_height="wrap_content"> 
    <LinearLayout android:id="@+id/scroll_content" 
     android:orientation="vertical" android:layout_width="fill_parent" 
     android:layout_height="fill_parent"> 
    </LinearLayout> 
</ScrollView> 

item_element.xml:

<?xml version="1.0" encoding="utf-8"?> 

<CheckBox android:id="@+id/checkbox" android:layout_width="wrap_content" 
    android:layout_height="wrap_content" android:layout_alignParentRight="true" 
    android:layout_marginRight="5dip" /> 

<TextView android:id="@+id/item_name" android:layout_width="wrap_content" 
    android:layout_height="wrap_content" android:textSize="18sp" 
    android:layout_centerVertical="true" android:layout_toLeftOf="@id/checkbox" 
    android:layout_alignParentLeft="true" /> 

<TextView android:id="@+id/item_id" android:layout_width="wrap_content" 
    android:layout_height="wrap_content" android:visibility="invisible" /> 

8

todo el mundo. Parece que descubrí esto. El estado de las casillas de verificación está siendo alterado en onRestoreInstanceState (Bundle). Este método se llama después de onCreate() (más precisamente, después de onStart()), y es otro lugar donde Android recomienda restaurar el estado.

Ahora, no tengo idea de por qué mis casillas de verificación están siendo alteradas en onRestoreInstanceState, pero al menos sé que es donde está ocurriendo el problema. Sorprendentemente, cuando anulo en RestoreInstanceState y no hago absolutamente nada (sin invocar a super.onRestoreInstanceState) toda la actividad funciona perfectamente.

Si alguien puede decirme por qué se está cambiando el estado seleccionado de las casillas de verificación en este método, me gustaría mucho saber. En mi opinión, esto parece un error dentro del código de Android en sí.

+3

El estado de las casillas de verificación se está alterando (reteniendo) porque tienen la propiedad 'saveEnabled' establecida en true, la predeterminada. Consulte [ver documentos] (http://developer.android.com/reference/android/view/View.html#setSaveEnabled (boolean)). Incluso tres años después de que esta pregunta se realizó inicialmente, esta función de Android continúa sorprendiendo a los desarrolladores. –

+0

Gracias por la sugerencia. Recientemente tuve un problema similar mientras escribía un patrón de MVVM personalizado ... en cualquier sentido, como MVVM administraba el estado de la vista, tuve que establecer myView.setSaveEnabled (false). – worked

2

Si alguna vez encuentras algo más sobre este problema, házmelo saber. Me enfrenté esencialmente a este mismo problema, y ​​solo anulé onRestoreInstanceState() funcionó. Muy raro.

1

Probablemente el problema es que cada vez que se llama onRestoreInstanceState(Bundle), restablece algunas o todas las "configuraciones" de su aplicación a los "valores predeterminados" de inicio. La mejor manera de resolver esto es a través de llamadas y administración de métodos del ciclo de vida de la aplicación. Además, cada vez que se cambia algo en su aplicación que no desea perder, guarde el cambio en saveInstanceState(Bundle) o cree su propio Bundle() para mantener los cambios temporalmente hasta que pueda persistir los cambios en un archivo o algo así, es bastante fácil pasar un paquete a través de un Intento entre Actividades. Cómo guarda lo que necesita guardar según el tiempo que necesita para conservar la "configuración".

1

Me he encontrado con esto también. Debería restaurar el estado de la casilla de verificación en onRestoreInstanceState() en lugar de onCreate().

La actividad se destruye cuando cambia la orientación de la pantalla y se llama a onRestoreInstanceState() después de onCreate(). Dado que la implementación principal/predeterminada de onRestoreInstanceState() restaura automáticamente el estado en Views with IDs, está restaurando sus casillas de verificación después de onCreate() y destruyéndolas, aparentemente debido a que tienen la misma ID (¿error de framework?).

http://developer.android.com/reference/android/app/Activity.html

http://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState(android.os.Bundle)

+0

Esto está sucediendo debido al campo 'saveEnabled'. Consulte [Ver documentos] (http://developer.android.com/reference/android/view/View.html#setSaveEnabled (boolean)). Definitivamente parece haber un error donde las vistas con la misma identificación tienen su estado restaurado a solo la última instancia de esa vista. –

1

También puede utilizar onSaveInstanceState(Bundle savedInstanceState) y onRestoreInstanceState(Bundle savedInstanceState)

ejemplo

@Override 
public void onSaveInstanceState(Bundle savedInstanceState) 
{ 
    // EditTexts text 
    savedInstanceState.putString("first_et",((EditText)findViewById(R.id.et_first)).getText().toString()); 
    // EditTexts text 
    savedInstanceState.putInt("first_et_v", ((EditText)findViewById(R.id.et_first)).getVisibility()); 
    super.onSaveInstanceState(savedInstanceState); 
} 

@Override 
public void onRestoreInstanceState(Bundle savedInstanceState) 
{ 
    super.onRestoreInstanceState(savedInstanceState); 
    // EditTexts text 
    ((EditText)findViewById(R.id.et_first)).setText(savedInstanceState.getString("first_et")); 
    // EditText visibility 
    ((EditText)findViewById(R.id.et_first)).setVisibility(savedInstanceState.getInt("first_et_v")); 
} 
41

tuve un problema similar. La aplicación tenía varias casillas de verificación en la pantalla.
Después de rotar la aplicación del teléfono fue el valor de configuración "manual" para todas las casillas de verificación.
Este código se ejecutó en onStart().
Pero en la pantalla todas las casillas de verificación se configuraron con el valor de 'última casilla' en la pantalla.
Android llamaba aRestoreInstanceState (..) y de alguna manera estaba tratando todas las casillas de verificación como 'uno' [último de la pantalla].

solución se desactivar 'la restauración de estado de la instancia':

<RadioButton 
    ... 
    android:saveEnabled="false" /> 
+3

Este es exactamente el problema. De todos estos otros comentarios es obvio que esta característica no es bien entendida por la mayoría de los desarrolladores de Android. –

+1

very nice awnser – Francois

+0

Esto junto con el almacenamiento del estado del botón dentro deSavedInstanceState() me ayudó a resolver mi problema –

0

Hay una solución que es más fácil de explicar.

Android CHECKBOXES y RADIOBUTTON y RADIOGROUPS se comportan de forma extraña si el atributo "id" no está establecido en ellos.

Tuve exactamente el mismo problema en mi código, y después de colocar identificadores en casillas de verificación, comenzó a funcionar, sin tener que deshabilitar ninguno de los métodos de superclase.

Cuestiones relacionadas