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 17 package android.widget; 18 19 import com.android.internal.R; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Canvas; 24 import android.graphics.drawable.Drawable; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.util.AttributeSet; 28 import android.view.Gravity; 29 import android.view.ViewDebug; 30 import android.view.accessibility.AccessibilityEvent; 31 import 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 */ 47 public 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.setChecked(mChecked); 215 } 216 217 @Override 218 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 219 super.onInitializeAccessibilityNodeInfo(info); 220 info.setCheckable(true); 221 info.setChecked(mChecked); 222 } 223 224 @Override 225 protected void onDraw(Canvas canvas) { 226 super.onDraw(canvas); 227 228 final Drawable buttonDrawable = mButtonDrawable; 229 if (buttonDrawable != null) { 230 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; 231 final int height = buttonDrawable.getIntrinsicHeight(); 232 233 int y = 0; 234 235 switch (verticalGravity) { 236 case Gravity.BOTTOM: 237 y = getHeight() - height; 238 break; 239 case Gravity.CENTER_VERTICAL: 240 y = (getHeight() - height) / 2; 241 break; 242 } 243 244 buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height); 245 buttonDrawable.draw(canvas); 246 } 247 } 248 249 @Override 250 protected int[] onCreateDrawableState(int extraSpace) { 251 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 252 if (isChecked()) { 253 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 254 } 255 return drawableState; 256 } 257 258 @Override 259 protected void drawableStateChanged() { 260 super.drawableStateChanged(); 261 262 if (mButtonDrawable != null) { 263 int[] myDrawableState = getDrawableState(); 264 265 // Set the state of the Drawable 266 mButtonDrawable.setState(myDrawableState); 267 268 invalidate(); 269 } 270 } 271 272 @Override 273 protected boolean verifyDrawable(Drawable who) { 274 return super.verifyDrawable(who) || who == mButtonDrawable; 275 } 276 277 @Override 278 public void jumpDrawablesToCurrentState() { 279 super.jumpDrawablesToCurrentState(); 280 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState(); 281 } 282 283 static class SavedState extends BaseSavedState { 284 boolean checked; 285 286 /** 287 * Constructor called from {@link CompoundButton#onSaveInstanceState()} 288 */ 289 SavedState(Parcelable superState) { 290 super(superState); 291 } 292 293 /** 294 * Constructor called from {@link #CREATOR} 295 */ 296 private SavedState(Parcel in) { 297 super(in); 298 checked = (Boolean)in.readValue(null); 299 } 300 301 @Override 302 public void writeToParcel(Parcel out, int flags) { 303 super.writeToParcel(out, flags); 304 out.writeValue(checked); 305 } 306 307 @Override 308 public String toString() { 309 return "CompoundButton.SavedState{" 310 + Integer.toHexString(System.identityHashCode(this)) 311 + " checked=" + checked + "}"; 312 } 313 314 public static final Parcelable.Creator<SavedState> CREATOR 315 = new Parcelable.Creator<SavedState>() { 316 public SavedState createFromParcel(Parcel in) { 317 return new SavedState(in); 318 } 319 320 public SavedState[] newArray(int size) { 321 return new SavedState[size]; 322 } 323 }; 324 } 325 326 @Override 327 public Parcelable onSaveInstanceState() { 328 // Force our ancestor class to save its state 329 setFreezesText(true); 330 Parcelable superState = super.onSaveInstanceState(); 331 332 SavedState ss = new SavedState(superState); 333 334 ss.checked = isChecked(); 335 return ss; 336 } 337 338 @Override 339 public void onRestoreInstanceState(Parcelable state) { 340 SavedState ss = (SavedState) state; 341 342 super.onRestoreInstanceState(ss.getSuperState()); 343 setChecked(ss.checked); 344 requestLayout(); 345 } 346 } 347