【问题标题】:Android CheckBox -- Restoring State After Screen RotationAndroid CheckBox -- 屏幕旋转后恢复状态
【发布时间】:2011-01-31 12:07:39
【问题描述】:

在屏幕旋转后尝试恢复CheckBoxes 列表的状态时,我遇到了一些非常意外(并且令人难以置信的令人沮丧)的功能。我想我首先会尝试在没有代码的情况下给出文本解释,以防有人能够在没有所有血腥细节的情况下确定解决方案。如果有人需要更多详细信息,我可以发布代码。

我有一个包含CheckBoxes 的复杂Views 的滚动列表。在屏幕旋转后,我未能成功恢复这些复选框的状态。我已经实现了onSaveInstanceState 并成功地将选中的复选框列表转移到onCreate 方法中。这是通过将数据库 ID 的 long[] 传递给 Bundle 来处理的。

在onCreate() 中,我检查Bundle 中的id 数组。如果数组在那里,我用它来确定在构建列表时要检查哪些复选框。我创建了许多测试方法,并根据 id 数组确认复选框设置正确。作为最后一项检查,我正在检查onCreate( 末尾的所有复选框的状态。一切看起来都不错...除非我旋转屏幕。

当我旋转屏幕时,会发生以下两种情况之一:1) 如果选中了任意数量的复选框,除了最后一个,所有复选框在旋转后都会关闭。 2) 如果最后一个复选框在旋转前被选中,那么所有的复选框在旋转后都被选中。

就像我说的,我检查了onCreate() 末尾的框的状态。问题是,onCreate 末尾的框的状态是正确,基于我在旋转之前选择的内容。但是,屏幕上框的状态并不能反映这一点。

此外,我已经实现了每个复选框的setOnCheckChangedListener(),并且我已经确认我的复选框的状态在我的onCreate 方法返回之后被改变了。

有人知道发生了什么吗?为什么我的onCreate 方法返回后复选框的状态会发生变化?

提前感谢您的帮助。几天来,我一直在尝试对此进行调试。在我发现我的复选框显然在我自己的代码之外的某个地方发生了变化后,我认为是时候四处询问了。

【问题讨论】:

  • 我相信 onResume 在您进行方向更改时会在 onCreate 之后调用。 onResume 中发生了什么?

标签: android checkbox rotation restore


【解决方案1】:

Rpond,我没有覆盖 onResume,所以我认为这不是问题。这里是 Activity 和相关的布局供大家查看。在代码中,您会看到许多 Log.d 语句(有时甚至更多。)通过这些 Log 语句,我能够验证代码是否完全按照我的预期工作。另外,请注意我添加到每个 CheckBox 的 onCheckChangedListener。它所做的只是打印一条日志语句,告诉我我的一个复选框的状态已更改。正是通过这个,我能够确定在我的 onCreate 返回后复选框的状态正在改变。您将看到我如何在 onCreate 结束时调用 inspectCheckboxes()。由此产生的日志语句不是旋转后在我的屏幕上显示的内容,我可以看到我的盒子的状态随后被改变(因为 onCheckChangedListener。)

SelectItems.java:

公共类 SelectItems 扩展 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" />

【讨论】:

    【解决方案2】:

    每个人。看来我想通了。复选框的状态正在 onRestoreInstanceState(Bundle) 中更改。此方法在 onCreate() 之后调用(更准确地说,在 onStart() 之后),是 Android 推荐恢复状态的另一个地方。

    现在,我不知道为什么我的复选框在 onRestoreInstanceState 中被更改,但至少我知道问题出在哪里。令人惊讶的是,当我覆盖 onRestoreInstanceState 并且什么都不做(不调用 super.onRestoreInstanceState)时,整个 Activity 完美运行。

    如果有人能告诉我为什么复选框的选中状态在这种方法中被更改,我非常想知道。在我看来,这看起来像是 Android 代码本身的一个错误。

    【讨论】:

    • 复选框的状态正在更改(保留),因为它们的属性 saveEnabled 设置为 true,默认值。见view docs。即使在最初提出这个问题三年后,这个 Android 功能仍然让开发人员感到惊讶。
    • 感谢您的提示。我最近在编写自定义 MVVM 模式时遇到了类似的问题......无论如何,由于 MVVM 正在管理视图状态,我必须设置 myView.setSaveEnabled(false)。
    【解决方案3】:

    如果您发现有关此问题的更多信息,请告诉我。我基本上遇到了同样的问题,只有覆盖 onRestoreInstanceState() 才有效。很奇怪。

    【讨论】:

      【解决方案4】:

      问题可能在于,每当调用 onRestoreInstanceState(Bundle) 时,它会将应用程序的部分或全部“设置”重置为启动时的“默认值”。解决此问题的最佳方法是通过应用程序生命周期方法调用和管理。此外,每当您的应用程序发生您不想丢失的更改时,请将更改保存到 saveInstanceState(Bundle) 或创建您自己的 Bundle() 以暂时保留更改,直到您可以将更改保存在文件或其他内容中, 它很容易通过活动之间的 Intent 传递捆绑包。如何保存需要保存的内容取决于您需要保留“设置”的时间。

      【讨论】:

        【解决方案5】:

        我也遇到过。您应该在 onRestoreInstanceState() 而不是 onCreate() 中恢复复选框状态。

        当屏幕方向改变并且在 onCreate() 之后调用 onRestoreInstanceState() 时,Activity 被销毁。由于 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)

        【讨论】:

        • 这是由于saveEnabled 字段而发生的。请参阅View docs。确实似乎存在一个错误,即具有相同 id 的视图的状态仅恢复到该视图的最后一个实例。
        【解决方案6】:

        您也可以使用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"));
        }
        

        【讨论】:

          【解决方案7】:

          我有类似的问题。应用在屏幕上有几个复选框。
          旋转手机应用程序后,所有复选框都是“手动”设置值。
          此代码在 onStart() 中执行。
          但在屏幕上,所有复选框的值都设置为屏幕上的“最后一个复选框”。
          Android 正在调用 onRestoreInstanceState(..) 并且以某种方式将所有复选框视为“一个”[最后一个来自屏幕]。

          解决方案是禁用“恢复实例状态”:

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

          【讨论】:

          • 这正是问题所在。从所有这些其他 cmets 中,很明显大多数 android 开发人员都不太了解此功能。
          • 这与在 onSavedInstanceState() 中存储按钮的状态一起帮助解决了我的问题
          • 解决我的问题的唯一方法。由于状态已保存,屏幕旋转后,系统自动将其设置为 true 后调用 switch 委托。我正在使用 Xamarin,在 C# 中,您必须在更改开关、复选框之前删除委托,以便它们不会通过手动设置执行;手动设置后,我们必须恢复代理。由于系统没有这样做,因此触发了代表。发现了 Grzegorz Dev。谢谢!
          • 哇,真是个难题!我在 DialogFragment 中使用复选框在 XML 中设置 ID 来扩展视图的多个实例,并且它们都一直在检查方向更改。我相信相同的 ID 是罪魁祸首 - 所有复选框都获得了最后添加的复选框的值。使用这个答案解决了这个问题。
          • 我喜欢这类问题。那些你花了几个小时敲打头的地方,却遇到了这样的事情。谢谢你,先生。
          【解决方案8】:

          有一个更容易解释的解决方案。

          如果没有在它们上设置“id”属性,Android CHECKBOXES 和 RADIOBUTTON 和 RADIOGROUPS 的行为会很奇怪。

          我的代码中遇到了完全相同的问题,在我将 id 放在复选框上之后,它开始工作,而无需禁用任何超类方法。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多