web-dev-qa-db-ja.com

Android CheckBox-画面回転後の状態の復元

画面の回転後にCheckBoxesのリストの状態を復元しようとしているときに、非常に予期しない(そして非常にイライラする)機能に遭遇しました。誰かがすべての厄介な詳細なしで解決策を決定できる場合に備えて、私は最初にコードなしでテキストによる説明をしようとすると思いました。誰かがより多くの詳細を必要とするならば、私はコードを投稿することができます。

Viewesを含む複雑なCheckBoxsのスクロールリストがあります。画面を回転させた後、これらのチェックボックスの状態を復元できませんでした。 onSaveInstanceStateを実装し、選択したチェックボックスのリストをonCreateメソッドに正常に転送しました。これは、データベースIDの_long[]_をBundleに渡すことによって処理されます。

onCreate()で、IDの配列についてBundleをチェックします。配列が存在する場合は、それを使用して、リストの作成時にチェックするチェックボックスを決定します。いくつかのテストメソッドを作成し、id配列に基づいてチェックボックスが正しく設定されていることを確認しました。最後のチェックとして、_onCreate(_)の最後にあるすべてのチェックボックスの状態をチェックしています。画面を回転させない限り、すべてが良さそうです。

画面を回転すると、次の2つのいずれかが発生します。1)最後のチェックボックスを除いて、任意の数のチェックボックスが選択されている場合、回転後にすべてのチェックボックスがオフになります。 2)ローテーション前に最後のチェックボックスがオンになっている場合、すべてのチェックボックスはローテーション後にチェック済みになります。

私が言ったように、私はonCreate()の最後にあるボックスの状態をチェックします。問題は、onCreateの最後のボックスの状態は、ローテーション前に選択したものに基づいて正しいです。ただし、画面上のボックスの状態はこれを反映していません。

さらに、各チェックボックスのsetOnCheckChangedListener()を実装し、チェックボックスの状態が変更されていることを確認しましたafter私のonCreateメソッドが返されます。

誰が何が起こっているのか考えていますか? onCreateメソッドが戻った後、チェックボックスの状態が変わるのはなぜですか?

よろしくお願いします。私はこれを数日間デグブしようとしています。チェックボックスが自分のコードの外のどこかで明らかに変化していることに気付いた後、私は周りに尋ねる時が来たと思いました。

29
Jared M

私も同様の問題を抱えていました。アプリの画面にはいくつかのチェックボックスがありました。
電話アプリをローテーションした後、すべてのチェックボックスの値を「手動で」設定しました。
このコードはonStart()で実行されました。
しかし、画面上では、すべてのチェックボックスが画面上の「最後のチェックボックス」の値で設定されていました。
AndroidはonRestoreInstanceState(..)を呼び出していて、どういうわけかすべてのチェックボックスを「1つ」として扱っていました[画面の最後]。

解決策は、「インスタンス状態の復元」を無効にすることでした。

<RadioButton
    ...
    Android:saveEnabled="false" />
49
Grzegorz Dev

全員。私はこれを理解したようです。チェックボックスの状態は、onRestoreInstanceState(Bundle)で変更されています。このメソッドはonCreate()の後で(より正確にはonStart()の後で)呼び出され、別の場所ですAndroid状態の復元をお勧めします。

これで、onRestoreInstanceState内でチェックボックスが変更される理由がわかりませんが、少なくとも、問題が発生している場所はわかっています。驚いたことに、onRestoreInstanceStateをオーバーライドして何もしない(super.onRestoreInstanceStateを呼び出さない)と、アクティビティ全体が完全に機能します。

誰かが私に言うことができるならなぜチェックボックスの選択された状態がこの方法で変更されていることを私は非常に知りたいです。私の意見では、これはAndroidコード自体のバグのように見えます。

8
Jared M

この問題について他に何か見つけたことがあれば、私に知らせてください。私は本質的にこれとまったく同じ問題に直面し、onRestoreInstanceState()をオーバーライドするだけで機能しました。非常に奇妙な。

2
ExistentialEnso

私もこれに遭遇しました。 onCreate()ではなくonRestoreInstanceState()でチェックボックスの状態を復元する必要があります。

画面の向きが変わり、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)

1
JustinC

Rpond、onResumeをオーバーライドしていないので、それが問題だとは思いません。これは、誰もが見ることができるアクティビティと関連するレイアウトです。コードには、多くのLog.dステートメントが表示されます(一度にさらに多くのステートメントがありました)。これらのLogステートメントを使用して、コードが期待どおりに機能することを確認できました。また、各チェックボックスに追加したonCheckChangedListenerにも注目してください。チェックボックスの1つの状態が変更されたことを通知するLogステートメントを出力するだけです。これにより、onCreateが戻った後にチェックボックスの状態が変更されていることを確認できました。 onCreateの最後にexamineCheckboxes()を呼び出す方法がわかります。これから生成されたLogステートメントは、ローテーション後に画面に表示されるものではなく、後でボックスの状態が変更されていることを確認できます(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" />
1
Jared M

おそらく問題は、onRestoreInstanceState(Bundle)が呼び出されるたびに、アプリの「設定」の一部またはすべてが起動時の「デフォルト」にリセットされることです。これを解決する最善の方法は、アプリのライフサイクルメソッドの呼び出しと管理を使用することです。さらに、アプリで失いたくない変更があった場合は、変更をsaveInstanceState(Bundle)に保存するか、独自のBundle()を作成して、変更が可能になるまで一時的に保持しますファイルなどの変更を永続化します。アクティビティ間のインテントを介してバンドルを渡すのに十分簡単です。 「設定」を保持する必要がある期間に応じて、保存する必要があるものをどのように保存するか。

1
Kingsolmn

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

説明しやすい解決策があります。

AndroidのチェックボックスとRADIOBUTTONおよびRADIOGROUPSは、「id」属性が設定されていない場合、奇妙な動作をします。

コードにもまったく同じ問題があり、チェックボックスにIDを配置すると、スーパークラスのメソッドを無効にすることなく機能し始めました。

0
Waqas Ahmed