RadioGroup.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 17package android.widget; 18 19import com.android.internal.R; 20 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.util.AttributeSet; 24import android.view.View; 25import 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 */ 52public 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 */ 65 public RadioGroup(Context context) { 66 super(context); 67 setOrientation(VERTICAL); 68 init(); 69 } 70 71 /** 72 * {@inheritDoc} 73 */ 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 94 private void init() { 95 mChildOnCheckedChangeListener = new CheckedStateTracker(); 96 mPassThroughListener = new PassThroughHierarchyChangeListener(); 97 super.setOnHierarchyChangeListener(mPassThroughListener); 98 } 99 100 /** 101 * {@inheritDoc} 102 */ 103 @Override 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 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 /** 126 * <p>Sets the selection to the radio button whose identifier is passed in 127 * parameter. Using -1 as the selection identifier clears the selection; 128 * such an operation is equivalent to invoking {@link #clearCheck()}.</p> 129 * 130 * @param id the unique id of the radio button to select in this group 131 * 132 * @see #getCheckedRadioButtonId() 133 * @see #clearCheck() 134 */ 135 public void check(int id) { 136 // don't even bother 137 if (id != -1 && (id == mCheckedId)) { 138 return; 139 } 140 141 if (mCheckedId != -1) { 142 setCheckedStateForView(mCheckedId, false); 143 } 144 145 if (id != -1) { 146 setCheckedStateForView(id, true); 147 } 148 149 setCheckedId(id); 150 } 151 152 private void setCheckedId(int id) { 153 mCheckedId = id; 154 if (mOnCheckedChangeListener != null) { 155 mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId); 156 } 157 } 158 159 private void setCheckedStateForView(int viewId, boolean checked) { 160 View checkedView = findViewById(viewId); 161 if (checkedView != null && checkedView instanceof RadioButton) { 162 ((RadioButton) checkedView).setChecked(checked); 163 } 164 } 165 166 /** 167 * <p>Returns the identifier of the selected radio button in this group. 168 * Upon empty selection, the returned value is -1.</p> 169 * 170 * @return the unique id of the selected radio button in this group 171 * 172 * @see #check(int) 173 * @see #clearCheck() 174 */ 175 public int getCheckedRadioButtonId() { 176 return mCheckedId; 177 } 178 179 /** 180 * <p>Clears the selection. When the selection is cleared, no radio button 181 * in this group is selected and {@link #getCheckedRadioButtonId()} returns 182 * null.</p> 183 * 184 * @see #check(int) 185 * @see #getCheckedRadioButtonId() 186 */ 187 public void clearCheck() { 188 check(-1); 189 } 190 191 /** 192 * <p>Register a callback to be invoked when the checked radio button 193 * changes in this group.</p> 194 * 195 * @param listener the callback to call on checked state change 196 */ 197 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 198 mOnCheckedChangeListener = listener; 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public LayoutParams generateLayoutParams(AttributeSet attrs) { 206 return new RadioGroup.LayoutParams(getContext(), attrs); 207 } 208 209 /** 210 * {@inheritDoc} 211 */ 212 @Override 213 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 214 return p instanceof RadioGroup.LayoutParams; 215 } 216 217 @Override 218 protected LinearLayout.LayoutParams generateDefaultLayoutParams() { 219 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 220 } 221 222 /** 223 * <p>This set of layout parameters defaults the width and the height of 224 * the children to {@link #WRAP_CONTENT} when they are not specified in the 225 * XML file. Otherwise, this class ussed the value read from the XML file.</p> 226 * 227 * <p>See 228 * {@link android.R.styleable#LinearLayout_Layout LinearLayout Attributes} 229 * for a list of all child view attributes that this class supports.</p> 230 * 231 */ 232 public static class LayoutParams extends LinearLayout.LayoutParams { 233 /** 234 * {@inheritDoc} 235 */ 236 public LayoutParams(Context c, AttributeSet attrs) { 237 super(c, attrs); 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 public LayoutParams(int w, int h) { 244 super(w, h); 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 public LayoutParams(int w, int h, float initWeight) { 251 super(w, h, initWeight); 252 } 253 254 /** 255 * {@inheritDoc} 256 */ 257 public LayoutParams(ViewGroup.LayoutParams p) { 258 super(p); 259 } 260 261 /** 262 * {@inheritDoc} 263 */ 264 public LayoutParams(MarginLayoutParams source) { 265 super(source); 266 } 267 268 /** 269 * <p>Fixes the child's width to 270 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's 271 * height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} 272 * when not specified in the XML file.</p> 273 * 274 * @param a the styled attributes set 275 * @param widthAttr the width attribute to fetch 276 * @param heightAttr the height attribute to fetch 277 */ 278 @Override 279 protected void setBaseAttributes(TypedArray a, 280 int widthAttr, int heightAttr) { 281 282 if (a.hasValue(widthAttr)) { 283 width = a.getLayoutDimension(widthAttr, "layout_width"); 284 } else { 285 width = WRAP_CONTENT; 286 } 287 288 if (a.hasValue(heightAttr)) { 289 height = a.getLayoutDimension(heightAttr, "layout_height"); 290 } else { 291 height = WRAP_CONTENT; 292 } 293 } 294 } 295 296 /** 297 * <p>Interface definition for a callback to be invoked when the checked 298 * radio button changed in this group.</p> 299 */ 300 public interface OnCheckedChangeListener { 301 /** 302 * <p>Called when the checked radio button has changed. When the 303 * selection is cleared, checkedId is -1.</p> 304 * 305 * @param group the group in which the checked radio button has changed 306 * @param checkedId the unique identifier of the newly checked radio button 307 */ 308 public void onCheckedChanged(RadioGroup group, int checkedId); 309 } 310 311 private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener { 312 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 313 // prevents from infinite recursion 314 if (mProtectFromCheckedChange) { 315 return; 316 } 317 318 mProtectFromCheckedChange = true; 319 if (mCheckedId != -1) { 320 setCheckedStateForView(mCheckedId, false); 321 } 322 mProtectFromCheckedChange = false; 323 324 int id = buttonView.getId(); 325 setCheckedId(id); 326 } 327 } 328 329 /** 330 * <p>A pass-through listener acts upon the events and dispatches them 331 * to another listener. This allows the table layout to set its own internal 332 * hierarchy change listener without preventing the user to setup his.</p> 333 */ 334 private class PassThroughHierarchyChangeListener implements 335 ViewGroup.OnHierarchyChangeListener { 336 private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener; 337 338 /** 339 * {@inheritDoc} 340 */ 341 public void onChildViewAdded(View parent, View child) { 342 if (parent == RadioGroup.this && child instanceof RadioButton) { 343 int id = child.getId(); 344 // generates an id if it's missing 345 if (id == View.NO_ID) { 346 id = child.hashCode(); 347 child.setId(id); 348 } 349 ((RadioButton) child).setOnCheckedChangeWidgetListener( 350 mChildOnCheckedChangeListener); 351 } 352 353 if (mOnHierarchyChangeListener != null) { 354 mOnHierarchyChangeListener.onChildViewAdded(parent, child); 355 } 356 } 357 358 /** 359 * {@inheritDoc} 360 */ 361 public void onChildViewRemoved(View parent, View child) { 362 if (parent == RadioGroup.this && child instanceof RadioButton) { 363 ((RadioButton) child).setOnCheckedChangeWidgetListener(null); 364 } 365 366 if (mOnHierarchyChangeListener != null) { 367 mOnHierarchyChangeListener.onChildViewRemoved(parent, child); 368 } 369 } 370 } 371} 372