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