Android CheckBox - восстановление состояния после вращения экрана
Я столкнулся с очень неожиданной (и невероятно расстраивающей) функциональностью, пытаясь восстановить состояние списка CheckBox после поворота экрана. Я решил, что сначала попытаюсь дать текстовое объяснение без кода, если кто-то сможет определить решение без всех подробностей. Если кому-то нужно больше деталей, я могу опубликовать код.
У меня есть прокручиваемый список сложных представлений, содержащих CheckBoxes. Я не смог восстановить состояние этих флажков после поворота экрана. Я реализовал onSaveInstanceState и успешно передал список выбранных флажков методу onCreate. Это обрабатывается путем передачи длинного [] идентификаторов базы данных в Bundle.
В onCreate() я проверяю Bundle для массива идентификаторов. Если массив существует, я использую его, чтобы определить, какие флажки проверяются при построении списка. Я создал несколько методов тестирования и подтвердил правильность установки флажков на основе массива id. В качестве последней проверки я проверяю состояния всех флажков в самом конце onCreate(). Все выглядит хорошо... если я не поворачиваю экран.
Когда я поворачиваю экран, происходит одна из двух вещей: 1) Если выбрано любое количество флажков, кроме последнего, все флажки отключены после поворота. 2) Если последний флажок установлен перед вращением, все флажки проверяются после поворота.
Как я уже сказал, я проверяю состояние ящиков в самом конце моего onCreate(). Дело в том, что состояние ящиков в конце onCreate правильное на основе того, что я выбрал до поворота. Однако состояние ящиков на экране не отражает этого.
Кроме того, я выполнил каждый флажок setOnCheckChangedListener(), и я подтвердил, что состояние моих флажков изменяется после возврата метода onCreate.
У кого-нибудь есть идея, что происходит? Почему состояние моих флажков изменяется после возврата метода onCreate?
Заранее благодарим за помощь. Я пытаюсь разложить это на пару дней. После того, как я обнаружил, что мои флажки, очевидно, меняются где-то за пределами моего собственного кода, я решил, что пришло время спросить.
Ответы
Ответ 1
У меня была аналогичная проблема. У приложения было несколько флажков на экране.
После поворота приложения для телефона было установлено значение "вручную" для всех флажков.
Этот код был выполнен в onStart().
Но на экране все флажки были установлены со значением "последний флажок" на экране.
Android вызывал onRestoreInstanceState (..) и каким-то образом обрабатывал все флажки как "один" [последний с экрана].
Решение должно было отключить "восстановление состояния экземпляра":
<RadioButton
...
android:saveEnabled="false" />
Ответ 2
все. Похоже, я понял это. Состояние флажков изменяется в onRestoreInstanceState (Bundle). Этот метод вызывается после onCreate() (точнее, после onStart()), и это другое место, где Android рекомендует восстанавливать состояние.
Теперь я понятия не имею, почему мои флажки изменяются внутри onRestoreInstanceState, но, по крайней мере, я знаю, что проблема возникает. Удивительно, но когда я переопределяю onRestoreInstanceState и ничего не делаю (нет вызова super.onRestoreInstanceState), вся деятельность работает отлично.
Если кто-нибудь скажет мне, почему выбранное состояние флажков изменяется в этом методе, я бы очень хотел узнать. По-моему, это выглядит как ошибка в самом коде Android.
Ответ 3
Если вы когда-нибудь найдете что-нибудь еще об этой проблеме, сообщите мне. Я столкнулся с этой же проблемой, и только переопределение onRestoreInstanceState()
сработало. Очень странно.
Ответ 4
Rpond, я не переоценил onResume, поэтому я не думаю, что это проблема. Вот активность и связанные макеты для всех. В коде вы увидите множество операторов Log.d(в какой-то момент их было еще больше.) С помощью этих операторов Log я смог проверить, работает ли код точно так, как я ожидаю. Также обратите внимание на onCheckChangedListener, который я добавляю в каждую CheckBox. Все, что он делает, это распечатать выражение журнала, в котором сообщается, что состояние одного из моих CheckBoxes изменено. Именно благодаря этому я смог определить, что состояние CheckBoxes было изменено после возвращения моего onCreate. Вы увидите, как я вызываю метод checkCheckboxes() в конце моего onCreate. Операторы Log, созданные из этого, не являются тем, что показано на моем экране после поворота, и я вижу, как состояние моих ящиков изменяется после этого (из-за onCheckChangedListener.)
SelectItems.java:
public class 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" />
Ответ 5
Вероятно, проблема заключается в том, что всякий раз, когда вызывается onRestoreInstanceState(Bundle)
, он сбрасывает некоторые или все "настройки" вашего приложения на начальные "значения по умолчанию". Лучший способ решить это - вызвать вызовы и управление методом жизненного цикла приложений. Кроме того, в любое время, когда что-то изменилось в вашем приложении, которое вы не хотите потерять, сохраните изменение в saveInstanceState(Bundle)
или создайте свой собственный Bundle()
, чтобы временно удерживать изменения, пока вы не сможете сохранить изменения в файле или что-то еще, Его достаточно легко передать комплект через намерение между действиями. Как вы сохраняете все, что вам нужно, чтобы сохранить в зависимости от того, как долго вам нужно сохранить "настройки".
Ответ 6
Я тоже столкнулся с этим. Вы должны восстановить состояние флажка в onRestoreInstanceState() вместо onCreate().
Активность разрушается, когда изменения ориентации экрана и onRestoreInstanceState() вызывается после onCreate(). Поскольку исходная/стандартная реализация onRestoreInstanceState() автоматически восстанавливает состояние в представлениях с идентификаторами, он восстанавливает ваши флажки после onCreate() и сбивает их - по-видимому, из-за того, что у них одинаковый идентификатор (фрейм-ошибка?).
http://developer.android.com/reference/android/app/Activity.html
http://developer.android.com/reference/android/app/Activity.html#onRestoreInstanceState (android.os.Bundle)
Ответ 7
Вы также можете использовать 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"));
}
Ответ 8
Есть решение, которое проще объяснить.
Android CHECKBOXES и RADIOBUTTON и RADIOGROUPS ведут себя странно, если на них не установлен атрибут id.
У меня была такая же проблема в моем коде, и после того, как я установил флажки в флажках, он начал работать без необходимости отключать какие-либо методы суперкласса.