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