• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import com.android.internal.R;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.util.AttributeSet;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.accessibility.AccessibilityEvent;
27 import android.view.accessibility.AccessibilityNodeInfo;
28 
29 
30 /**
31  * <p>This class is used to create a multiple-exclusion scope for a set of radio
32  * buttons. Checking one radio button that belongs to a radio group unchecks
33  * any previously checked radio button within the same group.</p>
34  *
35  * <p>Intially, all of the radio buttons are unchecked. While it is not possible
36  * to uncheck a particular radio button, the radio group can be cleared to
37  * remove the checked state.</p>
38  *
39  * <p>The selection is identified by the unique id of the radio button as defined
40  * in the XML layout file.</p>
41  *
42  * <p><strong>XML Attributes</strong></p>
43  * <p>See {@link android.R.styleable#RadioGroup RadioGroup Attributes},
44  * {@link android.R.styleable#LinearLayout LinearLayout Attributes},
45  * {@link android.R.styleable#ViewGroup ViewGroup Attributes},
46  * {@link android.R.styleable#View View Attributes}</p>
47  * <p>Also see
48  * {@link android.widget.LinearLayout.LayoutParams LinearLayout.LayoutParams}
49  * for layout attributes.</p>
50  *
51  * @see RadioButton
52  *
53  */
54 public class RadioGroup extends LinearLayout {
55     // holds the checked id; the selection is empty by default
56     private int mCheckedId = -1;
57     // tracks children radio buttons checked state
58     private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
59     // when true, mOnCheckedChangeListener discards events
60     private boolean mProtectFromCheckedChange = false;
61     private OnCheckedChangeListener mOnCheckedChangeListener;
62     private PassThroughHierarchyChangeListener mPassThroughListener;
63 
64     /**
65      * {@inheritDoc}
66      */
RadioGroup(Context context)67     public RadioGroup(Context context) {
68         super(context);
69         setOrientation(VERTICAL);
70         init();
71     }
72 
73     /**
74      * {@inheritDoc}
75      */
RadioGroup(Context context, AttributeSet attrs)76     public RadioGroup(Context context, AttributeSet attrs) {
77         super(context, attrs);
78 
79         // retrieve selected radio button as requested by the user in the
80         // XML layout file
81         TypedArray attributes = context.obtainStyledAttributes(
82                 attrs, com.android.internal.R.styleable.RadioGroup, com.android.internal.R.attr.radioButtonStyle, 0);
83 
84         int value = attributes.getResourceId(R.styleable.RadioGroup_checkedButton, View.NO_ID);
85         if (value != View.NO_ID) {
86             mCheckedId = value;
87         }
88 
89         final int index = attributes.getInt(com.android.internal.R.styleable.RadioGroup_orientation, VERTICAL);
90         setOrientation(index);
91 
92         attributes.recycle();
93         init();
94     }
95 
init()96     private void init() {
97         mChildOnCheckedChangeListener = new CheckedStateTracker();
98         mPassThroughListener = new PassThroughHierarchyChangeListener();
99         super.setOnHierarchyChangeListener(mPassThroughListener);
100     }
101 
102     /**
103      * {@inheritDoc}
104      */
105     @Override
setOnHierarchyChangeListener(OnHierarchyChangeListener listener)106     public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
107         // the user listener is delegated to our pass-through listener
108         mPassThroughListener.mOnHierarchyChangeListener = listener;
109     }
110 
111     /**
112      * {@inheritDoc}
113      */
114     @Override
onFinishInflate()115     protected void onFinishInflate() {
116         super.onFinishInflate();
117 
118         // checks the appropriate radio button as requested in the XML file
119         if (mCheckedId != -1) {
120             mProtectFromCheckedChange = true;
121             setCheckedStateForView(mCheckedId, true);
122             mProtectFromCheckedChange = false;
123             setCheckedId(mCheckedId);
124         }
125     }
126 
127     @Override
addView(View child, int index, ViewGroup.LayoutParams params)128     public void addView(View child, int index, ViewGroup.LayoutParams params) {
129         if (child instanceof RadioButton) {
130             final RadioButton button = (RadioButton) child;
131             if (button.isChecked()) {
132                 mProtectFromCheckedChange = true;
133                 if (mCheckedId != -1) {
134                     setCheckedStateForView(mCheckedId, false);
135                 }
136                 mProtectFromCheckedChange = false;
137                 setCheckedId(button.getId());
138             }
139         }
140 
141         super.addView(child, index, params);
142     }
143 
144     /**
145      * <p>Sets the selection to the radio button whose identifier is passed in
146      * parameter. Using -1 as the selection identifier clears the selection;
147      * such an operation is equivalent to invoking {@link #clearCheck()}.</p>
148      *
149      * @param id the unique id of the radio button to select in this group
150      *
151      * @see #getCheckedRadioButtonId()
152      * @see #clearCheck()
153      */
check(int id)154     public void check(int id) {
155         // don't even bother
156         if (id != -1 && (id == mCheckedId)) {
157             return;
158         }
159 
160         if (mCheckedId != -1) {
161             setCheckedStateForView(mCheckedId, false);
162         }
163 
164         if (id != -1) {
165             setCheckedStateForView(id, true);
166         }
167 
168         setCheckedId(id);
169     }
170 
setCheckedId(int id)171     private void setCheckedId(int id) {
172         mCheckedId = id;
173         if (mOnCheckedChangeListener != null) {
174             mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
175         }
176     }
177 
setCheckedStateForView(int viewId, boolean checked)178     private void setCheckedStateForView(int viewId, boolean checked) {
179         View checkedView = findViewById(viewId);
180         if (checkedView != null && checkedView instanceof RadioButton) {
181             ((RadioButton) checkedView).setChecked(checked);
182         }
183     }
184 
185     /**
186      * <p>Returns the identifier of the selected radio button in this group.
187      * Upon empty selection, the returned value is -1.</p>
188      *
189      * @return the unique id of the selected radio button in this group
190      *
191      * @see #check(int)
192      * @see #clearCheck()
193      *
194      * @attr ref android.R.styleable#RadioGroup_checkedButton
195      */
getCheckedRadioButtonId()196     public int getCheckedRadioButtonId() {
197         return mCheckedId;
198     }
199 
200     /**
201      * <p>Clears the selection. When the selection is cleared, no radio button
202      * in this group is selected and {@link #getCheckedRadioButtonId()} returns
203      * null.</p>
204      *
205      * @see #check(int)
206      * @see #getCheckedRadioButtonId()
207      */
clearCheck()208     public void clearCheck() {
209         check(-1);
210     }
211 
212     /**
213      * <p>Register a callback to be invoked when the checked radio button
214      * changes in this group.</p>
215      *
216      * @param listener the callback to call on checked state change
217      */
setOnCheckedChangeListener(OnCheckedChangeListener listener)218     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
219         mOnCheckedChangeListener = listener;
220     }
221 
222     /**
223      * {@inheritDoc}
224      */
225     @Override
generateLayoutParams(AttributeSet attrs)226     public LayoutParams generateLayoutParams(AttributeSet attrs) {
227         return new RadioGroup.LayoutParams(getContext(), attrs);
228     }
229 
230     /**
231      * {@inheritDoc}
232      */
233     @Override
checkLayoutParams(ViewGroup.LayoutParams p)234     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
235         return p instanceof RadioGroup.LayoutParams;
236     }
237 
238     @Override
generateDefaultLayoutParams()239     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {
240         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
241     }
242 
243     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)244     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
245         super.onInitializeAccessibilityEvent(event);
246         event.setClassName(RadioGroup.class.getName());
247     }
248 
249     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)250     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
251         super.onInitializeAccessibilityNodeInfo(info);
252         info.setClassName(RadioGroup.class.getName());
253     }
254 
255     /**
256      * <p>This set of layout parameters defaults the width and the height of
257      * the children to {@link #WRAP_CONTENT} when they are not specified in the
258      * XML file. Otherwise, this class ussed the value read from the XML file.</p>
259      *
260      * <p>See
261      * {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes}
262      * for a list of all child view attributes that this class supports.</p>
263      *
264      */
265     public static class LayoutParams extends LinearLayout.LayoutParams {
266         /**
267          * {@inheritDoc}
268          */
LayoutParams(Context c, AttributeSet attrs)269         public LayoutParams(Context c, AttributeSet attrs) {
270             super(c, attrs);
271         }
272 
273         /**
274          * {@inheritDoc}
275          */
LayoutParams(int w, int h)276         public LayoutParams(int w, int h) {
277             super(w, h);
278         }
279 
280         /**
281          * {@inheritDoc}
282          */
LayoutParams(int w, int h, float initWeight)283         public LayoutParams(int w, int h, float initWeight) {
284             super(w, h, initWeight);
285         }
286 
287         /**
288          * {@inheritDoc}
289          */
LayoutParams(ViewGroup.LayoutParams p)290         public LayoutParams(ViewGroup.LayoutParams p) {
291             super(p);
292         }
293 
294         /**
295          * {@inheritDoc}
296          */
LayoutParams(MarginLayoutParams source)297         public LayoutParams(MarginLayoutParams source) {
298             super(source);
299         }
300 
301         /**
302          * <p>Fixes the child's width to
303          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
304          * height to  {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
305          * when not specified in the XML file.</p>
306          *
307          * @param a the styled attributes set
308          * @param widthAttr the width attribute to fetch
309          * @param heightAttr the height attribute to fetch
310          */
311         @Override
setBaseAttributes(TypedArray a, int widthAttr, int heightAttr)312         protected void setBaseAttributes(TypedArray a,
313                 int widthAttr, int heightAttr) {
314 
315             if (a.hasValue(widthAttr)) {
316                 width = a.getLayoutDimension(widthAttr, "layout_width");
317             } else {
318                 width = WRAP_CONTENT;
319             }
320 
321             if (a.hasValue(heightAttr)) {
322                 height = a.getLayoutDimension(heightAttr, "layout_height");
323             } else {
324                 height = WRAP_CONTENT;
325             }
326         }
327     }
328 
329     /**
330      * <p>Interface definition for a callback to be invoked when the checked
331      * radio button changed in this group.</p>
332      */
333     public interface OnCheckedChangeListener {
334         /**
335          * <p>Called when the checked radio button has changed. When the
336          * selection is cleared, checkedId is -1.</p>
337          *
338          * @param group the group in which the checked radio button has changed
339          * @param checkedId the unique identifier of the newly checked radio button
340          */
onCheckedChanged(RadioGroup group, int checkedId)341         public void onCheckedChanged(RadioGroup group, int checkedId);
342     }
343 
344     private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
onCheckedChanged(CompoundButton buttonView, boolean isChecked)345         public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
346             // prevents from infinite recursion
347             if (mProtectFromCheckedChange) {
348                 return;
349             }
350 
351             mProtectFromCheckedChange = true;
352             if (mCheckedId != -1) {
353                 setCheckedStateForView(mCheckedId, false);
354             }
355             mProtectFromCheckedChange = false;
356 
357             int id = buttonView.getId();
358             setCheckedId(id);
359         }
360     }
361 
362     /**
363      * <p>A pass-through listener acts upon the events and dispatches them
364      * to another listener. This allows the table layout to set its own internal
365      * hierarchy change listener without preventing the user to setup his.</p>
366      */
367     private class PassThroughHierarchyChangeListener implements
368             ViewGroup.OnHierarchyChangeListener {
369         private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;
370 
371         /**
372          * {@inheritDoc}
373          */
onChildViewAdded(View parent, View child)374         public void onChildViewAdded(View parent, View child) {
375             if (parent == RadioGroup.this && child instanceof RadioButton) {
376                 int id = child.getId();
377                 // generates an id if it's missing
378                 if (id == View.NO_ID) {
379                     id = View.generateViewId();
380                     child.setId(id);
381                 }
382                 ((RadioButton) child).setOnCheckedChangeWidgetListener(
383                         mChildOnCheckedChangeListener);
384             }
385 
386             if (mOnHierarchyChangeListener != null) {
387                 mOnHierarchyChangeListener.onChildViewAdded(parent, child);
388             }
389         }
390 
391         /**
392          * {@inheritDoc}
393          */
onChildViewRemoved(View parent, View child)394         public void onChildViewRemoved(View parent, View child) {
395             if (parent == RadioGroup.this && child instanceof RadioButton) {
396                 ((RadioButton) child).setOnCheckedChangeWidgetListener(null);
397             }
398 
399             if (mOnHierarchyChangeListener != null) {
400                 mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
401             }
402         }
403     }
404 }
405