2010-03-24 24 views
26

Ho riscontrato alcune funzionalità inattese (e incredibilmente frustranti) durante il tentativo di ripristinare lo stato di un elenco di CheckBox dopo una rotazione dello schermo. Ho pensato che prima avrei cercato di dare una spiegazione testuale senza il codice, nel caso qualcuno fosse in grado di determinare una soluzione senza tutti i dettagli cruenti. Se qualcuno ha bisogno di maggiori dettagli, posso postare il codice.CheckBox Android - Ripristino dello stato dopo la rotazione dello schermo

Ho una lista a scorrimento di complessi View s che contengono CheckBox es. Non sono riuscito a ripristinare lo stato di queste caselle di controllo dopo una rotazione dello schermo. Ho implementato onSaveInstanceState e ho trasferito con successo l'elenco delle caselle di controllo selezionate al metodo onCreate. Questo viene gestito passando un long[] di ID database su Bundle.

In onCreate() Controllo lo Bundle per l'array di ID. Se la matrice è lì, la utilizzo per determinare quali caselle di controllo controllare quando viene creato l'elenco. Ho creato una serie di metodi di test e ho confermato che le caselle di controllo sono state impostate correttamente, in base all'array ID. Come ultimo controllo, sto verificando gli stati di tutte le caselle di controllo alla fine di onCreate(). Tutto sembra a posto ... a meno che non ruoti lo schermo.

Quando ruoto lo schermo, accade una delle due cose: 1) Se viene selezionato un numero qualsiasi di caselle di controllo, tranne l'ultimo, tutte le caselle di controllo sono disattivate dopo una rotazione. 2) Se l'ultima casella di controllo è selezionata prima della rotazione, tutte le caselle di controllo sono selezionate dopo la rotazione.

Come ho detto, controllo lo stato delle scatole alla fine del mio onCreate(). Il fatto è che lo stato delle caselle alla fine di onCreate è corretto in base a ciò che ho selezionato prima della rotazione. Tuttavia, lo stato delle caselle sullo schermo non riflette questo.

Inoltre, ho implementato setOnCheckChangedListener() e mi hanno confermato che dello stato le mie caselle di controllo vengono alterati dopo miei onCreate metodo restituisce casella di ogni controllo.

Qualcuno ha un'idea di cosa sta succedendo? Perché lo stato delle mie caselle di controllo cambia dopo la restituzione del metodo onCreate?

Grazie in anticipo per il vostro aiuto. Ho cercato di decifrarlo per un paio di giorni. Dopo aver scoperto che le mie caselle di controllo stavano apparentemente cambiando da qualche parte fuori dal mio codice, ho pensato che fosse giunto il momento di chiedere in giro.

+0

Credo che suResume venga chiamato dopo onCreate quando si esegue un cambiamento di orientamento. Qualcosa sta succedendo? –

risposta

1

Rpond, non ho sovrascritto onResume, quindi non credo che sia il problema. Ecco l'attività e i layout associati a cui tutti possono vedere. Nel codice vedrete molte istruzioni Log.d (ce n'erano ancora di più a un certo punto). Con queste istruzioni Log sono riuscito a verificare che il codice funzioni esattamente come mi aspetto. Inoltre, notare onCheckChangedListener che aggiungo a ciascun CheckBox. Tutto ciò che fa è stampare una dichiarazione Log che mi dice lo stato di uno dei miei CheckBox modificati. È stato in questo modo che sono stato in grado di determinare lo stato dei CheckBox che venivano modificati dopo il ritorno di onCreate. Vedrai come chiamo examineCheckboxes() alla fine del mio onCreate. Le istruzioni Log prodotte da questo non sono ciò che viene visualizzato sullo schermo dopo una rotazione e posso vedere lo stato delle mie caselle essere modificato in seguito (a causa di onCheckChangedListener.)

SelectItems.java:

SelectItems public class estende Activity {

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

tutti. Sembra che l'ho capito. Lo stato delle caselle di controllo è stato modificato in onRestoreInstanceState (Bundle). Questo metodo viene chiamato dopo onCreate() (più precisamente, dopo onStart()), ed è un altro posto che Android raccomanda di ripristinare lo stato.

Ora, non ho idea del perché le mie caselle di controllo siano state modificate all'interno di onRestoreInstanceState, ma almeno so che è dove si sta verificando il problema. Sorprendentemente, quando sovrascrivo onRestoreInstanceState e non faccio assolutamente nulla (nessuna chiamata a super.onRestoreInstanceState) l'intera attività funziona perfettamente.

Se qualcuno può dirmi perché lo stato selezionato delle caselle di controllo è stato modificato in questo metodo mi piacerebbe molto sapere. A mio avviso questo sembra un bug all'interno del codice Android stesso.

+3

Lo stato delle caselle di controllo è stato modificato (mantenuto) perché hanno la proprietà 'saveEnabled' impostata su true, l'impostazione predefinita. Vedi [visualizza documenti] (http://developer.android.com/reference/android/view/View.html#setSaveEnabled (booleano)). Anche a distanza di tre anni dalla domanda iniziale, questa funzione Android continua a sorprendere gli sviluppatori. –

+0

Grazie per il suggerimento. Recentemente ho avuto un problema simile durante la scrittura di un pattern MVVM personalizzato ... in ogni caso, poiché MVVM gestiva lo stato di visualizzazione, dovevo impostare myView.setSaveEnabled (false). – worked

2

Se dovessi trovare qualcos'altro di più su questo problema, faccelo sapere. Ho affrontato essenzialmente questo stesso identico problema, e ho solo sostituito il onRestoreInstanceState(). Molto strano.

1

Probabilmente il problema è che ogni volta che si chiama onRestoreInstanceState(Bundle) azzera alcune o tutte le "Impostazioni" della vostra applicazione per le start-up "default". Il modo migliore per risolverlo è attraverso chiamate e gestione dei metodi del ciclo di vita dell'app. Inoltre, ogni volta che nella tua app viene modificato qualcosa che non vuoi perdere, salva la modifica allo saveInstanceState(Bundle) o crea il tuo Bundle() per mantenere temporaneamente le modifiche finché non puoi mantenere le modifiche in un file o qualcosa del genere, È abbastanza facile passare un pacco attraverso un intento tra le attività. Come si salva tutto ciò che è necessario salvare in base a quanto tempo è necessario mantenere le "impostazioni".

1

Ho incontrato anche questo. È necessario ripristinare lo stato della casella di controllo in onRestoreInstanceState() anziché onCreate().

L'attività viene distrutta quando cambia l'orientamento dello schermo e onRestoreInstanceState() viene chiamato dopo onCreate(). Dal momento che l'implementazione genitore/predefinito di onRestoreInstanceState() ripristina automaticamente lo stato in Views with IDs, ripristina le caselle di controllo dopo onCreate() e li danneggia, a quanto pare che abbiano lo stesso ID (bug del framework?).

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

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

+0

Questo sta accadendo a causa del campo 'saveEnabled'. Vedi [Visualizza documenti] (http://developer.android.com/reference/android/view/View.html#setSaveEnabled (booleano)). Sembra esserci un bug in cui le viste con lo stesso id hanno il loro stato ripristinato solo sull'ultima istanza di quella vista. –

1

È inoltre possibile utilizzare onSaveInstanceState(Bundle savedInstanceState) e onRestoreInstanceState(Bundle savedInstanceState)

esempio

@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

Ho avuto problemi simili. App aveva diverse caselle di controllo sullo schermo.
Dopo aver ruotato l'app del telefono è stato impostato "manualmente" il valore per tutte le caselle di controllo.
Questo codice è stato eseguito su onStart().
Ma sullo schermo tutte le caselle sono state impostate con il valore di "ultima casella di controllo" sullo schermo.
Android stava chiamando suRestoreInstanceState (..) e in qualche modo stava trattando tutte le caselle di controllo come "uno" [ultimo dallo schermo].

soluzione dovesse disabilitare 'ripristinare lo stato esempio':

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

Questo è esattamente il problema. Da tutti questi altri commenti è ovvio che questa funzione non è ben compresa dalla maggior parte degli sviluppatori Android. –

+1

molto piacevole awnser – Francois

+0

Questo insieme con la memorizzazione dello stato del pulsante all'interno di onSavedInstanceState() ha aiutato a risolvere il mio problema –

0

C'è una soluzione che è più facile da spiegare.

Android CHECKBOX e RADIOBUTTON e RADIOGROUPS si comportano in modo strano se l'attributo "id" non è impostato su di essi.

Ho avuto lo stesso identico problema nel mio codice e, dopo aver inserito ID nelle caselle di controllo, ha iniziato a funzionare, senza dover disabilitare nessuno dei metodi della superclasse.

Problemi correlati