Android CheckBox — Restoring State After Screen Rotation

不羁的心 提交于 2019-11-28 18:15:59

I had similar problem. App had several checkboxes on screen.
After rotating phone app was 'manually' setting value for all checkboxes.
This code was executed in onStart().
But on screen all checkboxes were set with value of 'last checkbox' on screen.
Android was calling onRestoreInstanceState(..) and somehow was treating all checkboxes as 'one' [last from screen].

Solution were to disable 'restoring instance state':

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

everyone. Looks like I figured this out. The state of the check boxes is being altered in onRestoreInstanceState(Bundle). This method is called after onCreate() (more precisely, after onStart()), and is another place Android recommends restoring state.

Now, I have no idea why my check boxes are being altered within onRestoreInstanceState, but at least I know that is where the problem is occurring. Surprisingly, when I override onRestoreInstanceState and do absolutely nothing (no call to super.onRestoreInstanceState) the entire Activity works perfectly.

If anyone can tell me why the check boxes' selected state is being changed in this method I would very much like to know. In my opinion this looks like a bug within the Android code itself.

ExistentialEnso

If you ever find anything else more about this problem, please let me know. I faced essentially this exact same problem, and only overriding onRestoreInstanceState() worked. Very odd.

Rpond, I have not overriden onResume, so I don't think that is the issue. Here is the Activity and associated layouts for everyone to see. In the code you'll see many Log.d statements (there were even more at one point.) With these Log statements I was able to verify that the code works exactly as I expect it to. Also, notice the onCheckChangedListener I add to each CheckBox. All it does is print a Log statement telling me the state of one of my CheckBoxes changed. It was through this that I was able to determine the state of the CheckBoxes were being altered after my onCreate returns. You'll see how I call examineCheckboxes() at the end of my onCreate. The Log statements produced from this are not what is shown on my screen after a rotation, and I can see the state of my boxes being altered afterwards (because of the onCheckChangedListener.)

SelectItems.java:

public class SelectItems extends 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" />

Probably the issue is that whenever onRestoreInstanceState(Bundle) is called it resets some or all of the "settings" of your app to the start-up "defaults". The best way to solve this is through app lifecycle method calls and management. Additionally, any time something is changed in your app that you don't want to loose, save the change to the saveInstanceState(Bundle) or create your own Bundle() to hold the changes temporarily until you can persist the changes in a file or something, Its easy enough to pass a bundle through an Intent between Activities. How you save whatever you need to save depending on how long you need to retain the "settings".

I've run into this too. You should restore checkbox state in onRestoreInstanceState() instead of onCreate().

The activity is destroyed when screen orientation changes and onRestoreInstanceState() is called after onCreate(). Since the parent/default implementation of onRestoreInstanceState() automatically restores state in Views with IDs, it's restoring your checkboxes after onCreate() and clobbering them -- apparently due to them having the same ID (framework bug?).

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

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

bdely

You can also use onSaveInstanceState(Bundle savedInstanceState) and onRestoreInstanceState(Bundle savedInstanceState)

example

@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"));
}

There is a solution that's easier to explain.

Android CHECKBOXES and RADIOBUTTON and RADIOGROUPS behave weirdly if the "id" attribute is not set on them.

I had the exact same problem in my code, and after I placed ids on checkboxes, it started working, without having to disable any of superclass methods.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!