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 notifyViewAccessibilityStateChangedIfNeeded( 118 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 119 120 // Avoid infinite recursions if setChecked() is called from a listener 121 if (mBroadcasting) { 122 return; 123 } 124 125 mBroadcasting = true; 126 if (mOnCheckedChangeListener != null) { 127 mOnCheckedChangeListener.onCheckedChanged(this, mChecked); 128 } 129 if (mOnCheckedChangeWidgetListener != null) { 130 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked); 131 } 132 133 mBroadcasting = false; 134 } 135 } 136 137 /** 138 * Register a callback to be invoked when the checked state of this button 139 * changes. 140 * 141 * @param listener the callback to call on checked state change 142 */ 143 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 144 mOnCheckedChangeListener = listener; 145 } 146 147 /** 148 * Register a callback to be invoked when the checked state of this button 149 * changes. This callback is used for internal purpose only. 150 * 151 * @param listener the callback to call on checked state change 152 * @hide 153 */ 154 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) { 155 mOnCheckedChangeWidgetListener = listener; 156 } 157 158 /** 159 * Interface definition for a callback to be invoked when the checked state 160 * of a compound button changed. 161 */ 162 public static interface OnCheckedChangeListener { 163 /** 164 * Called when the checked state of a compound button has changed. 165 * 166 * @param buttonView The compound button view whose state has changed. 167 * @param isChecked The new checked state of buttonView. 168 */ 169 void onCheckedChanged(CompoundButton buttonView, boolean isChecked); 170 } 171 172 /** 173 * Set the background to a given Drawable, identified by its resource id. 174 * 175 * @param resid the resource id of the drawable to use as the background 176 */ 177 public void setButtonDrawable(int resid) { 178 if (resid != 0 && resid == mButtonResource) { 179 return; 180 } 181 182 mButtonResource = resid; 183 184 Drawable d = null; 185 if (mButtonResource != 0) { 186 d = getResources().getDrawable(mButtonResource); 187 } 188 setButtonDrawable(d); 189 } 190 191 /** 192 * Set the background to a given Drawable 193 * 194 * @param d The Drawable to use as the background 195 */ 196 public void setButtonDrawable(Drawable d) { 197 if (d != null) { 198 if (mButtonDrawable != null) { 199 mButtonDrawable.setCallback(null); 200 unscheduleDrawable(mButtonDrawable); 201 } 202 d.setCallback(this); 203 d.setVisible(getVisibility() == VISIBLE, false); 204 mButtonDrawable = d; 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 public int getCompoundPaddingLeft() { 228 int padding = super.getCompoundPaddingLeft(); 229 if (!isLayoutRtl()) { 230 final Drawable buttonDrawable = mButtonDrawable; 231 if (buttonDrawable != null) { 232 padding += buttonDrawable.getIntrinsicWidth(); 233 } 234 } 235 return padding; 236 } 237 238 @Override 239 public int getCompoundPaddingRight() { 240 int padding = super.getCompoundPaddingRight(); 241 if (isLayoutRtl()) { 242 final Drawable buttonDrawable = mButtonDrawable; 243 if (buttonDrawable != null) { 244 padding += buttonDrawable.getIntrinsicWidth(); 245 } 246 } 247 return padding; 248 } 249 250 /** 251 * @hide 252 */ 253 @Override 254 public int getHorizontalOffsetForDrawables() { 255 final Drawable buttonDrawable = mButtonDrawable; 256 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0; 257 } 258 259 @Override 260 protected void onDraw(Canvas canvas) { 261 super.onDraw(canvas); 262 263 final Drawable buttonDrawable = mButtonDrawable; 264 if (buttonDrawable != null) { 265 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; 266 final int drawableHeight = buttonDrawable.getIntrinsicHeight(); 267 final int drawableWidth = buttonDrawable.getIntrinsicWidth(); 268 269 int top = 0; 270 switch (verticalGravity) { 271 case Gravity.BOTTOM: 272 top = getHeight() - drawableHeight; 273 break; 274 case Gravity.CENTER_VERTICAL: 275 top = (getHeight() - drawableHeight) / 2; 276 break; 277 } 278 int bottom = top + drawableHeight; 279 int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; 280 int right = isLayoutRtl() ? getWidth() : drawableWidth; 281 282 buttonDrawable.setBounds(left, top, right, bottom); 283 buttonDrawable.draw(canvas); 284 } 285 } 286 287 @Override 288 protected int[] onCreateDrawableState(int extraSpace) { 289 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 290 if (isChecked()) { 291 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 292 } 293 return drawableState; 294 } 295 296 @Override 297 protected void drawableStateChanged() { 298 super.drawableStateChanged(); 299 300 if (mButtonDrawable != null) { 301 int[] myDrawableState = getDrawableState(); 302 303 // Set the state of the Drawable 304 mButtonDrawable.setState(myDrawableState); 305 306 invalidate(); 307 } 308 } 309 310 @Override 311 protected boolean verifyDrawable(Drawable who) { 312 return super.verifyDrawable(who) || who == mButtonDrawable; 313 } 314 315 @Override 316 public void jumpDrawablesToCurrentState() { 317 super.jumpDrawablesToCurrentState(); 318 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState(); 319 } 320 321 static class SavedState extends BaseSavedState { 322 boolean checked; 323 324 /** 325 * Constructor called from {@link CompoundButton#onSaveInstanceState()} 326 */ 327 SavedState(Parcelable superState) { 328 super(superState); 329 } 330 331 /** 332 * Constructor called from {@link #CREATOR} 333 */ 334 private SavedState(Parcel in) { 335 super(in); 336 checked = (Boolean)in.readValue(null); 337 } 338 339 @Override 340 public void writeToParcel(Parcel out, int flags) { 341 super.writeToParcel(out, flags); 342 out.writeValue(checked); 343 } 344 345 @Override 346 public String toString() { 347 return "CompoundButton.SavedState{" 348 + Integer.toHexString(System.identityHashCode(this)) 349 + " checked=" + checked + "}"; 350 } 351 352 public static final Parcelable.Creator<SavedState> CREATOR 353 = new Parcelable.Creator<SavedState>() { 354 public SavedState createFromParcel(Parcel in) { 355 return new SavedState(in); 356 } 357 358 public SavedState[] newArray(int size) { 359 return new SavedState[size]; 360 } 361 }; 362 } 363 364 @Override 365 public Parcelable onSaveInstanceState() { 366 // Force our ancestor class to save its state 367 setFreezesText(true); 368 Parcelable superState = super.onSaveInstanceState(); 369 370 SavedState ss = new SavedState(superState); 371 372 ss.checked = isChecked(); 373 return ss; 374 } 375 376 @Override 377 public void onRestoreInstanceState(Parcelable state) { 378 SavedState ss = (SavedState) state; 379 380 super.onRestoreInstanceState(ss.getSuperState()); 381 setChecked(ss.checked); 382 requestLayout(); 383 } 384} 385