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