2010-03-24 79 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放置后,它开始工作,无需禁用任何超类方法。