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