CompoundButton.java revision 8a78fd4d9572dff95432fcc4ba0e87563415b728
1/* 2 * Copyright (C) 2007 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.graphics.Canvas; 24import android.graphics.drawable.Drawable; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.util.AttributeSet; 28import android.view.Gravity; 29import android.view.ViewDebug; 30import android.view.accessibility.AccessibilityEvent; 31import android.view.accessibility.AccessibilityNodeInfo; 32 33/** 34 * <p> 35 * A button with two states, checked and unchecked. When the button is pressed 36 * or clicked, the state changes automatically. 37 * </p> 38 * 39 * <p><strong>XML attributes</strong></p> 40 * <p> 41 * See {@link android.R.styleable#CompoundButton 42 * CompoundButton Attributes}, {@link android.R.styleable#Button Button 43 * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link 44 * android.R.styleable#View View Attributes} 45 * </p> 46 */ 47public abstract class CompoundButton extends Button implements Checkable { 48 private boolean mChecked; 49 private int mButtonResource; 50 private boolean mBroadcasting; 51 private Drawable mButtonDrawable; 52 private OnCheckedChangeListener mOnCheckedChangeListener; 53 private OnCheckedChangeListener mOnCheckedChangeWidgetListener; 54 55 private static final int[] CHECKED_STATE_SET = { 56 R.attr.state_checked 57 }; 58 59 public CompoundButton(Context context) { 60 this(context, null); 61 } 62 63 public CompoundButton(Context context, AttributeSet attrs) { 64 this(context, attrs, 0); 65 } 66 67 public CompoundButton(Context context, AttributeSet attrs, int defStyle) { 68 super(context, attrs, defStyle); 69 70 TypedArray a = 71 context.obtainStyledAttributes( 72 attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0); 73 74 Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); 75 if (d != null) { 76 setButtonDrawable(d); 77 } 78 79 boolean checked = a 80 .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false); 81 setChecked(checked); 82 83 a.recycle(); 84 } 85 86 public void toggle() { 87 setChecked(!mChecked); 88 } 89 90 @Override 91 public boolean performClick() { 92 /* 93 * XXX: These are tiny, need some surrounding 'expanded touch area', 94 * which will need to be implemented in Button if we only override 95 * performClick() 96 */ 97 98 /* When clicked, toggle the state */ 99 toggle(); 100 return super.performClick(); 101 } 102 103 @ViewDebug.ExportedProperty 104 public boolean isChecked() { 105 return mChecked; 106 } 107 108 /** 109 * <p>Changes the checked state of this button.</p> 110 * 111 * @param checked true to check the button, false to uncheck it 112 */ 113 public void setChecked(boolean checked) { 114 if (mChecked != checked) { 115 mChecked = checked; 116 refreshDrawableState(); 117 118 // Avoid infinite recursions if setChecked() is called from a listener 119 if (mBroadcasting) { 120 return; 121 } 122 123 mBroadcasting = true; 124 if (mOnCheckedChangeListener != null) { 125 mOnCheckedChangeListener.onCheckedChanged(this, mChecked); 126 } 127 if (mOnCheckedChangeWidgetListener != null) { 128 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked); 129 } 130 131 mBroadcasting = false; 132 } 133 } 134 135 /** 136 * Register a callback to be invoked when the checked state of this button 137 * changes. 138 * 139 * @param listener the callback to call on checked state change 140 */ 141 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 142 mOnCheckedChangeListener = listener; 143 } 144 145 /** 146 * Register a callback to be invoked when the checked state of this button 147 * changes. This callback is used for internal purpose only. 148 * 149 * @param listener the callback to call on checked state change 150 * @hide 151 */ 152 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) { 153 mOnCheckedChangeWidgetListener = listener; 154 } 155 156 /** 157 * Interface definition for a callback to be invoked when the checked state 158 * of a compound button changed. 159 */ 160 public static interface OnCheckedChangeListener { 161 /** 162 * Called when the checked state of a compound button has changed. 163 * 164 * @param buttonView The compound button view whose state has changed. 165 * @param isChecked The new checked state of buttonView. 166 */ 167 void onCheckedChanged(CompoundButton buttonView, boolean isChecked); 168 } 169 170 /** 171 * Set the background to a given Drawable, identified by its resource id. 172 * 173 * @param resid the resource id of the drawable to use as the background 174 */ 175 public void setButtonDrawable(int resid) { 176 if (resid != 0 && resid == mButtonResource) { 177 return; 178 } 179 180 mButtonResource = resid; 181 182 Drawable d = null; 183 if (mButtonResource != 0) { 184 d = getResources().getDrawable(mButtonResource); 185 } 186 setButtonDrawable(d); 187 } 188 189 /** 190 * Set the background to a given Drawable 191 * 192 * @param d The Drawable to use as the background 193 */ 194 public void setButtonDrawable(Drawable d) { 195 if (d != null) { 196 if (mButtonDrawable != null) { 197 mButtonDrawable.setCallback(null); 198 unscheduleDrawable(mButtonDrawable); 199 } 200 d.setCallback(this); 201 d.setState(getDrawableState()); 202 d.setVisible(getVisibility() == VISIBLE, false); 203 mButtonDrawable = d; 204 mButtonDrawable.setState(null); 205 setMinHeight(mButtonDrawable.getIntrinsicHeight()); 206 } 207 208 refreshDrawableState(); 209 } 210 211 @Override 212 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 213 super.onInitializeAccessibilityEvent(event); 214 event.setClassName(CompoundButton.class.getName()); 215 event.setChecked(mChecked); 216 } 217 218 @Override 219 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 220 super.onInitializeAccessibilityNodeInfo(info); 221 info.setClassName(CompoundButton.class.getName()); 222 info.setCheckable(true); 223 info.setChecked(mChecked); 224 } 225 226 @Override 227 protected void onDraw(Canvas canvas) { 228 super.onDraw(canvas); 229 230 final Drawable buttonDrawable = mButtonDrawable; 231 if (buttonDrawable != null) { 232 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; 233 final int height = buttonDrawable.getIntrinsicHeight(); 234 235 int y = 0; 236 237 switch (verticalGravity) { 238 case Gravity.BOTTOM: 239 y = getHeight() - height; 240 break; 241 case Gravity.CENTER_VERTICAL: 242 y = (getHeight() - height) / 2; 243 break; 244 } 245 246 buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height); 247 buttonDrawable.draw(canvas); 248 } 249 } 250 251 @Override 252 protected int[] onCreateDrawableState(int extraSpace) { 253 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 254 if (isChecked()) { 255 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 256 } 257 return drawableState; 258 } 259 260 @Override 261 protected void drawableStateChanged() { 262 super.drawableStateChanged(); 263 264 if (mButtonDrawable != null) { 265 int[] myDrawableState = getDrawableState(); 266 267 // Set the state of the Drawable 268 mButtonDrawable.setState(myDrawableState); 269 270 invalidate(); 271 } 272 } 273 274 @Override 275 protected boolean verifyDrawable(Drawable who) { 276 return super.verifyDrawable(who) || who == mButtonDrawable; 277 } 278 279 @Override 280 public void jumpDrawablesToCurrentState() { 281 super.jumpDrawablesToCurrentState(); 282 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState(); 283 } 284 285 static class SavedState extends BaseSavedState { 286 boolean checked; 287 288 /** 289 * Constructor called from {@link CompoundButton#onSaveInstanceState()} 290 */ 291 SavedState(Parcelable superState) { 292 super(superState); 293 } 294 295 /** 296 * Constructor called from {@link #CREATOR} 297 */ 298 private SavedState(Parcel in) { 299 super(in); 300 checked = (Boolean)in.readValue(null); 301 } 302 303 @Override 304 public void writeToParcel(Parcel out, int flags) { 305 super.writeToParcel(out, flags); 306 out.writeValue(checked); 307 } 308 309 @Override 310 public String toString() { 311 return "CompoundButton.SavedState{" 312 + Integer.toHexString(System.identityHashCode(this)) 313 + " checked=" + checked + "}"; 314 } 315 316 public static final Parcelable.Creator<SavedState> CREATOR 317 = new Parcelable.Creator<SavedState>() { 318 public SavedState createFromParcel(Parcel in) { 319 return new SavedState(in); 320 } 321 322 public SavedState[] newArray(int size) { 323 return new SavedState[size]; 324 } 325 }; 326 } 327 328 @Override 329 public Parcelable onSaveInstanceState() { 330 // Force our ancestor class to save its state 331 setFreezesText(true); 332 Parcelable superState = super.onSaveInstanceState(); 333 334 SavedState ss = new SavedState(superState); 335 336 ss.checked = isChecked(); 337 return ss; 338 } 339 340 @Override 341 public void onRestoreInstanceState(Parcelable state) { 342 SavedState ss = (SavedState) state; 343 344 super.onRestoreInstanceState(ss.getSuperState()); 345 setChecked(ss.checked); 346 requestLayout(); 347 } 348} 349