2010-03-24 82 views
26

我試圖在屏幕旋轉後恢復列表CheckBox es的狀態時遇到了一些非常意想不到的(令人難以置信的令人沮喪的)功能。我想我會首先嚐試給出一個沒有代碼的文本解釋,以防某人能夠確定一個沒有所有血腥細節的解決方案。如果任何人需要更多的細節,我可以發佈代碼。Android複選框 - 屏幕旋轉後恢復狀態

我有一個包含CheckBox es的複雜View s的滾動列表。在屏幕旋轉後,我還沒有成功恢復這些複選框的狀態。我已實施onSaveInstanceState並已成功將所選複選框的列表傳送到onCreate方法。這通過將數據庫ID的long[]傳遞給Bundle來處理。

onCreate()我檢查Bundle爲id數組。如果數組在那裏,我使用它來確定在構建列表時要檢查哪些複選框。我已經創建了許多測試方法,並且已經根據id數組確認複選框正確設置。作爲最後一個檢查,我正在檢查onCreate(最後的所有複選框的狀態)。一切都很好......除非我旋轉屏幕。

當我旋轉屏幕時,會發生以下兩種情況之一:1)如果選中任意數量的複選框,除最後一個外,所有複選框都在旋轉後關閉。 2)如果最後一個複選框在旋轉前被選中,則所有複選框都是,旋轉後選中

就像我說的,我檢查我的onCreate()最後的框狀態。事情是,onCreate結束時的方框的狀態是正確的根據我在旋轉之前選擇的內容。但是,屏幕上方框的狀態並未反映出這一點。

另外,我已經實現了每個複選框的setOnCheckChangedListener(),我已經證實了我的複選框國家正在改變onCreate方法返回。

任何人都知道發生了什麼?爲什麼我的onCreate方法返回後,我的複選框的狀態會改變?

在此先感謝您的幫助。我一直試圖消除這一點現在幾天。當我發現我的複選框顯然在我自己的代碼之外的某個地方發生改變時,我想是時候到處打聽了。

+0

我相信onResume在onCreate之後被調用,當你做一個方向改變時。在onResume中發生了什麼? –

回答

1

Rpond,我沒有覆蓋onResume,所以我不認爲這是問題。這是活動和相關的佈局,供大家看到。在代碼中,您將看到許多Log.d語句(在一點上甚至更多)。通過這些Log語句,我可以驗證代碼的工作方式與我期望的完全相同。另外,請注意我添加到每個CheckBox的onCheckChangedListener。它所做的只是打印一條Log語句,告訴我一個CheckBox的狀態發生了變化。正是通過這一點,我才能確定在我的onCreate返回後CheckBoxes的狀態正在改變。你會看到我在我的onCreate結尾處如何調用examineCheckboxes()。由此產生的Log語句並不是在旋轉後顯示在我的屏幕上,我可以看到我的盒子的狀態在之後發生了變化(因爲onCheckChangedListener。)

SelectItems。的java:

公共類一個SelectItems延伸活動{

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

大家。看起來我明白了這一點。複選框的狀態正在onRestoreInstanceState(Bundle)中更改。此方法在onCreate()之後調用(更確切地說,在onStart()之後),並且是Android建議恢復狀態的另一個地方。

現在,我不知道爲什麼我的複選框在onRestoreInstanceState內被改變,但至少我知道這是問題發生的地方。令人驚訝的是,當我重寫onRestoreInstanceState並且完全沒有(沒有調用super.onRestoreInstanceState)時,整個Activity完美地工作。

如果有人能告訴我爲什麼複選框的選中狀態正在被改變,這種方法我非常想知道。在我看來,這看起來像是Android代碼本身的一個錯誤。

+3

複選框的狀態正在被更改(保留),因爲它們的屬性'saveEnabled'設置爲true,默認值。請參閱[查看文檔](http://developer.android.com/reference/android/view/View.html#setSaveEnabled(布爾值))。最初問起這個問題三年後,這款Android功能仍然讓開發者感到驚訝。 –

+0

感謝您的提示。我最近在編寫自定義MVVM模式時遇到了類似的問題......無論如何,由於MVVM正在管理視圖狀態,我不得不設置myView.setSaveEnabled(false)。 – worked

2

如果您還發現有關此問題的更多信息,請通知我。我基本上面對這個完全相同的問題,只有覆蓋onRestoreInstanceState()工作。很奇怪。

1

可能的問題是,無論何時調用onRestoreInstanceState(Bundle),它都會將應用程序的部分或全部「設置」重置爲啓動「默認值」。解決此問題的最佳方法是通過應用程序生命週期方法調用和管理。此外,只要您的應用程序中某些您不想丟失的內容發生變化,請將更改保存到saveInstanceState(Bundle)或創建您自己的Bundle()以暫時保存更改,直到您可以將更改保留在文件或其他內容中,通過活動之間的意圖傳遞一個包。根據您需要保留「設置」的時間長短,您如何保存需要保存的任何內容。

1

我也遇到過這個。您應該在onRestoreInstanceState()而不是onCreate()中恢復複選框狀態。

當屏幕方向更改並onCreate()後調用onRestoreInstanceState()時,活動將被銷燬。由於onRestoreInstanceState()的父/默認實現會自動使用ID恢復視圖中的狀態,因此它會在onCreate()之後恢復您的複選框並對其進行破壞 - 顯然是由於它們具有相同的ID(框架錯誤?)。

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

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

+0

這是由於'saveEnabled'字段發生的。請參閱[查看文檔](http://developer.android.com/reference/android/view/View.html#setSaveEnabled(布爾))。肯定會出現一個錯誤,其中具有相同ID的視圖將其狀態僅恢復到該視圖的最後一個實例。 –

1

您還可以使用onSaveInstanceState(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)

例如

@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

我有類似的問題。應用程序在屏幕上有幾個複選框。
旋轉手機應用程序是「手動」設置所有複選框的值。
此代碼在onStart()中執行。
但在屏幕上,所有複選框的值均爲'last checkbox'。
Android正在調用RestoreInstanceState(..),並以某種方式將所有複選框視爲'one'[last from screen]。

溶液中禁用「恢復實例狀態」:

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

這正是問題所在。從所有這些其他評論中可以明顯看出,大多數android開發人員並不能很好地理解這個特性。 –

+1

非常好的awnser – Francois

+0

這與在onSavedInstanceState()中存儲按鈕的狀態一起幫助解決了我的問題 –

0

有這容易解釋的解決方案。

Android CHECKBOXES和RADIOBUTTON和RADIOGROUPS的行爲很奇怪,如果它們沒有設置「id」屬性。

我在我的代碼中有完全相同的問題,並且在將複選框上的ID放置後,它開始工作,無需禁用任何超類方法。