RadioGroup#getCheckedRadioButtonId() が、チェックが入ったviewのidではなく、何番目にチェックが入っているかを取得できると勘違いして、実際その勘違い通りの振る舞いをするように見えるケースが存在した調査ログです。
結論から言ってしまうと、RadioButtonにidを振らないとView#generateViewId() が1から順に勝手に新規idを振ってしまい、それを返してしまうという話です。
現象
RadioButtonを3つ配置したRadioGroupが存在しており、そのうち1番目にチェックをつけた状態でRadioGroup#getCheckedRadioButtonId()が1を返すケースがある。 この振る舞いになる条件は、
- RadioButtonにidをふらない
- 初回起動時のみ(n回目に対象Activityを起動した際には3n-2が返ってくる)
具体的にはこんなレイアウト
<RadioGroup android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/radiogroup_sample"> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample1"/> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample2"/> <RadioButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/sample3"/> </RadioGroup>
何がおこったか
RadioButtonがviewのhierarchyに追加される時点で、idが存在するかチェックし 存在しない場合は新規に追加します。
RadioGroup.java
private class PassThroughHierarchyChangeListener implements ViewGroup.OnHierarchyChangeListener { private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; /** * {@inheritDoc} */ public void onChildViewAdded(View parent, View child) { if (parent == RadioGroup.this && child instanceof RadioButton) { int id = child.getId(); // generates an id if it's missing if (id == View.NO_ID) { id = View.generateViewId(); child.setId(id); }
View.java
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); public static int generateViewId() { for (;;) { final int result = sNextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. if (sNextGeneratedId.compareAndSet(result, newValue)) { return result; } } }
generateViewId() は1で初期化したAtomicIntegerから順にインクリメントした値を返すので、それぞれn番目がチェックされているかのように振舞っていたのです。(returnするのがnewValueではなくresultなので初回は1が返ってきます。) ただし、例えばActivityを再起動するなどして再度viewのhierarchyに追加されるときは1からではなく続きからインクリメントされるので、初回起動時のみn番目がチェックされているかのように見えていたというオチでした。
余談
何番目がチェックされているか返すmethodはRadioGroupを拡張したクラスでaddView() をoverrideしてやれば作れそう。 ただし、「何番目がチェックされているか」ではなく「何がチェックされているか」の方がどう考えても重要なのであまり使い道はないかもしれない。
public class CustomRadioGroup extends RadioGroup { private List<RadioButton> mChildren = new ArrayList<>(); public CustomRadioGroup(Context context) { super(context); } public CustomRadioGroup(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child, int index, ViewGroup.LayoutParams params) { if (child instanceof RadioButton) { mChildren.add((RadioButton) child); } super.addView(child, index, params); } public int getSelectedPosition() { int selectedViewId = getCheckedRadioButtonId(); if (selectedViewId == -1 || mChildren.isEmpty()) { return -1; } for (int i = 0; i < mChildren.size() -1; i++) { if (mChildren.get(i).getId() == selectedViewId) { return i + 1; } } return 0; } }