blob: 7d9d3052fe222f3d871460a1dcb836de45405ff7 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Alan Viverette91174362014-06-17 14:51:45 -070019import android.annotation.Nullable;
20import android.graphics.PorterDuff;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import com.android.internal.R;
22
23import android.content.Context;
Alan Viverette91174362014-06-17 14:51:45 -070024import android.content.res.ColorStateList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import android.content.res.TypedArray;
26import android.graphics.Canvas;
27import android.graphics.drawable.Drawable;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.util.AttributeSet;
31import android.view.Gravity;
Steve Zeigler7a367882010-02-23 16:39:08 -080032import android.view.ViewDebug;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070033import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov13774d22011-06-15 15:29:51 -070034import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
36/**
37 * <p>
38 * A button with two states, checked and unchecked. When the button is pressed
39 * or clicked, the state changes automatically.
40 * </p>
41 *
42 * <p><strong>XML attributes</strong></p>
43 * <p>
44 * See {@link android.R.styleable#CompoundButton
45 * CompoundButton Attributes}, {@link android.R.styleable#Button Button
46 * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
47 * android.R.styleable#View View Attributes}
48 * </p>
49 */
50public abstract class CompoundButton extends Button implements Checkable {
51 private boolean mChecked;
52 private int mButtonResource;
53 private boolean mBroadcasting;
Alan Viverette91174362014-06-17 14:51:45 -070054
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 private Drawable mButtonDrawable;
Alan Viverettea4264452014-07-28 16:02:55 -070056 private ColorStateList mButtonTintList = null;
Alan Viveretteb56f5d22014-09-14 15:48:50 -070057 private PorterDuff.Mode mButtonTintMode = null;
Alan Viverette91174362014-06-17 14:51:45 -070058 private boolean mHasButtonTint = false;
Alan Viveretteb56f5d22014-09-14 15:48:50 -070059 private boolean mHasButtonTintMode = false;
Alan Viverette91174362014-06-17 14:51:45 -070060
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 private OnCheckedChangeListener mOnCheckedChangeListener;
62 private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
63
64 private static final int[] CHECKED_STATE_SET = {
65 R.attr.state_checked
66 };
67
68 public CompoundButton(Context context) {
69 this(context, null);
70 }
71
72 public CompoundButton(Context context, AttributeSet attrs) {
73 this(context, attrs, 0);
74 }
75
Alan Viverette617feb92013-09-09 18:09:13 -070076 public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) {
77 this(context, attrs, defStyleAttr, 0);
78 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079
Alan Viverette617feb92013-09-09 18:09:13 -070080 public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
81 super(context, attrs, defStyleAttr, defStyleRes);
82
83 final TypedArray a = context.obtainStyledAttributes(
84 attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080085
Alan Viverette91174362014-06-17 14:51:45 -070086 final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 if (d != null) {
88 setButtonDrawable(d);
89 }
90
Alan Viveretteb56f5d22014-09-14 15:48:50 -070091 if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {
92 mButtonTintMode = Drawable.parseTintMode(a.getInt(
93 R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
94 mHasButtonTintMode = true;
95 }
Alan Viverette4f64c042014-07-21 17:49:13 -070096
Alan Viverette91174362014-06-17 14:51:45 -070097 if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
Alan Viverettea4264452014-07-28 16:02:55 -070098 mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
Alan Viverette91174362014-06-17 14:51:45 -070099 mHasButtonTint = true;
Alan Viverette91174362014-06-17 14:51:45 -0700100 }
101
102 final boolean checked = a.getBoolean(
103 com.android.internal.R.styleable.CompoundButton_checked, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 setChecked(checked);
105
106 a.recycle();
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700107
108 applyButtonTint();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800109 }
110
111 public void toggle() {
112 setChecked(!mChecked);
113 }
114
115 @Override
116 public boolean performClick() {
117 /*
118 * XXX: These are tiny, need some surrounding 'expanded touch area',
119 * which will need to be implemented in Button if we only override
120 * performClick()
121 */
122
123 /* When clicked, toggle the state */
124 toggle();
125 return super.performClick();
126 }
127
Steve Zeigler7a367882010-02-23 16:39:08 -0800128 @ViewDebug.ExportedProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 public boolean isChecked() {
130 return mChecked;
131 }
132
133 /**
134 * <p>Changes the checked state of this button.</p>
135 *
136 * @param checked true to check the button, false to uncheck it
137 */
138 public void setChecked(boolean checked) {
139 if (mChecked != checked) {
140 mChecked = checked;
141 refreshDrawableState();
Alan Viverette77e9a282013-09-12 17:16:09 -0700142 notifyViewAccessibilityStateChangedIfNeeded(
143 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144
145 // Avoid infinite recursions if setChecked() is called from a listener
146 if (mBroadcasting) {
147 return;
148 }
149
150 mBroadcasting = true;
151 if (mOnCheckedChangeListener != null) {
152 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
153 }
154 if (mOnCheckedChangeWidgetListener != null) {
155 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
156 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700157
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158 mBroadcasting = false;
159 }
160 }
161
162 /**
163 * Register a callback to be invoked when the checked state of this button
164 * changes.
165 *
166 * @param listener the callback to call on checked state change
167 */
168 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
169 mOnCheckedChangeListener = listener;
170 }
171
172 /**
173 * Register a callback to be invoked when the checked state of this button
174 * changes. This callback is used for internal purpose only.
175 *
176 * @param listener the callback to call on checked state change
177 * @hide
178 */
179 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
180 mOnCheckedChangeWidgetListener = listener;
181 }
182
183 /**
184 * Interface definition for a callback to be invoked when the checked state
185 * of a compound button changed.
186 */
187 public static interface OnCheckedChangeListener {
188 /**
189 * Called when the checked state of a compound button has changed.
190 *
191 * @param buttonView The compound button view whose state has changed.
192 * @param isChecked The new checked state of buttonView.
193 */
194 void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
195 }
196
197 /**
Alan Viverette91174362014-06-17 14:51:45 -0700198 * Set the button graphic to a given Drawable, identified by its resource
199 * id.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 *
Alan Viverette91174362014-06-17 14:51:45 -0700201 * @param resid the resource id of the drawable to use as the button
202 * graphic
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 */
204 public void setButtonDrawable(int resid) {
205 if (resid != 0 && resid == mButtonResource) {
206 return;
207 }
208
209 mButtonResource = resid;
210
211 Drawable d = null;
212 if (mButtonResource != 0) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800213 d = getContext().getDrawable(mButtonResource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 }
215 setButtonDrawable(d);
216 }
217
218 /**
Alan Viverette91174362014-06-17 14:51:45 -0700219 * Set the button graphic to a given Drawable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 *
Alan Viverette91174362014-06-17 14:51:45 -0700221 * @param d The Drawable to use as the button graphic
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 */
223 public void setButtonDrawable(Drawable d) {
Alan Viverette91174362014-06-17 14:51:45 -0700224 if (mButtonDrawable != d) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 if (mButtonDrawable != null) {
226 mButtonDrawable.setCallback(null);
227 unscheduleDrawable(mButtonDrawable);
228 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229
Alan Viverette91174362014-06-17 14:51:45 -0700230 mButtonDrawable = d;
231
232 if (d != null) {
233 d.setCallback(this);
234 d.setLayoutDirection(getLayoutDirection());
235 if (d.isStateful()) {
236 d.setState(getDrawableState());
237 }
238 d.setVisible(getVisibility() == VISIBLE, false);
239 setMinHeight(d.getIntrinsicHeight());
240 applyButtonTint();
241 }
242 }
243 }
244
245 /**
Alan Viverette91174362014-06-17 14:51:45 -0700246 * Applies a tint to the button drawable. Does not modify the current tint
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700247 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
Alan Viverette91174362014-06-17 14:51:45 -0700248 * <p>
249 * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
250 * automatically mutate the drawable and apply the specified tint and tint
251 * mode using
Alan Viverettea4264452014-07-28 16:02:55 -0700252 * {@link Drawable#setTintList(ColorStateList)}.
Alan Viverette91174362014-06-17 14:51:45 -0700253 *
254 * @param tint the tint to apply, may be {@code null} to clear tint
255 *
256 * @attr ref android.R.styleable#CompoundButton_buttonTint
Alan Viverettea4264452014-07-28 16:02:55 -0700257 * @see #setButtonTintList(ColorStateList)
258 * @see Drawable#setTintList(ColorStateList)
Alan Viverette91174362014-06-17 14:51:45 -0700259 */
Alan Viverettea4264452014-07-28 16:02:55 -0700260 public void setButtonTintList(@Nullable ColorStateList tint) {
261 mButtonTintList = tint;
Alan Viverette4f64c042014-07-21 17:49:13 -0700262 mHasButtonTint = true;
263
264 applyButtonTint();
Alan Viverette91174362014-06-17 14:51:45 -0700265 }
266
267 /**
268 * @return the tint applied to the button drawable
269 * @attr ref android.R.styleable#CompoundButton_buttonTint
Alan Viverettea4264452014-07-28 16:02:55 -0700270 * @see #setButtonTintList(ColorStateList)
Alan Viverette91174362014-06-17 14:51:45 -0700271 */
272 @Nullable
Alan Viverettea4264452014-07-28 16:02:55 -0700273 public ColorStateList getButtonTintList() {
274 return mButtonTintList;
Alan Viverette91174362014-06-17 14:51:45 -0700275 }
276
277 /**
278 * Specifies the blending mode used to apply the tint specified by
Alan Viverettea4264452014-07-28 16:02:55 -0700279 * {@link #setButtonTintList(ColorStateList)}} to the button drawable. The
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700280 * default mode is {@link PorterDuff.Mode#SRC_IN}.
Alan Viverette91174362014-06-17 14:51:45 -0700281 *
282 * @param tintMode the blending mode used to apply the tint, may be
283 * {@code null} to clear tint
284 * @attr ref android.R.styleable#CompoundButton_buttonTintMode
Alan Viverette4f64c042014-07-21 17:49:13 -0700285 * @see #getButtonTintMode()
Alan Viverettea4264452014-07-28 16:02:55 -0700286 * @see Drawable#setTintMode(PorterDuff.Mode)
Alan Viverette91174362014-06-17 14:51:45 -0700287 */
288 public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) {
Alan Viverette4f64c042014-07-21 17:49:13 -0700289 mButtonTintMode = tintMode;
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700290 mHasButtonTintMode = true;
Alan Viverette4f64c042014-07-21 17:49:13 -0700291
292 applyButtonTint();
Alan Viverette91174362014-06-17 14:51:45 -0700293 }
294
295 /**
296 * @return the blending mode used to apply the tint to the button drawable
297 * @attr ref android.R.styleable#CompoundButton_buttonTintMode
Alan Viverette4f64c042014-07-21 17:49:13 -0700298 * @see #setButtonTintMode(PorterDuff.Mode)
Alan Viverette91174362014-06-17 14:51:45 -0700299 */
300 @Nullable
301 public PorterDuff.Mode getButtonTintMode() {
302 return mButtonTintMode;
303 }
304
305 private void applyButtonTint() {
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700306 if (mButtonDrawable != null && (mHasButtonTint || mHasButtonTintMode)) {
Alan Viverette91174362014-06-17 14:51:45 -0700307 mButtonDrawable = mButtonDrawable.mutate();
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700308
309 if (mHasButtonTint) {
310 mButtonDrawable.setTintList(mButtonTintList);
311 }
312
313 if (mHasButtonTintMode) {
314 mButtonDrawable.setTintMode(mButtonTintMode);
315 }
Alan Viverette91174362014-06-17 14:51:45 -0700316 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 }
318
319 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700320 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
321 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800322 event.setClassName(CompoundButton.class.getName());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700323 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700324 }
325
326 @Override
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700327 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
328 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800329 info.setClassName(CompoundButton.class.getName());
Svetoslav Ganov0f55cc32011-07-17 10:51:49 -0700330 info.setCheckable(true);
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700331 info.setChecked(mChecked);
332 }
333
334 @Override
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700335 public int getCompoundPaddingLeft() {
336 int padding = super.getCompoundPaddingLeft();
337 if (!isLayoutRtl()) {
338 final Drawable buttonDrawable = mButtonDrawable;
339 if (buttonDrawable != null) {
340 padding += buttonDrawable.getIntrinsicWidth();
341 }
342 }
343 return padding;
344 }
345
346 @Override
347 public int getCompoundPaddingRight() {
348 int padding = super.getCompoundPaddingRight();
349 if (isLayoutRtl()) {
350 final Drawable buttonDrawable = mButtonDrawable;
351 if (buttonDrawable != null) {
352 padding += buttonDrawable.getIntrinsicWidth();
353 }
354 }
355 return padding;
356 }
357
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -0800358 /**
359 * @hide
360 */
361 @Override
362 public int getHorizontalOffsetForDrawables() {
363 final Drawable buttonDrawable = mButtonDrawable;
364 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
365 }
366
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700367 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 protected void onDraw(Canvas canvas) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 final Drawable buttonDrawable = mButtonDrawable;
370 if (buttonDrawable != null) {
371 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700372 final int drawableHeight = buttonDrawable.getIntrinsicHeight();
373 final int drawableWidth = buttonDrawable.getIntrinsicWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374
Alan Viverette61956602014-04-22 19:07:06 -0700375 final int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 switch (verticalGravity) {
377 case Gravity.BOTTOM:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700378 top = getHeight() - drawableHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 break;
380 case Gravity.CENTER_VERTICAL:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700381 top = (getHeight() - drawableHeight) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 break;
Alan Viverette61956602014-04-22 19:07:06 -0700383 default:
384 top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 }
Alan Viverette61956602014-04-22 19:07:06 -0700386 final int bottom = top + drawableHeight;
387 final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
388 final int right = isLayoutRtl() ? getWidth() : drawableWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700390 buttonDrawable.setBounds(left, top, right, bottom);
Alan Viverette61956602014-04-22 19:07:06 -0700391
392 final Drawable background = getBackground();
Alan Viverettec80ad992014-05-19 15:46:17 -0700393 if (background != null) {
Alan Viverette61956602014-04-22 19:07:06 -0700394 background.setHotspotBounds(left, top, right, bottom);
395 }
396 }
397
398 super.onDraw(canvas);
399
400 if (buttonDrawable != null) {
Alan Viveretteb95c3362014-10-17 17:19:12 -0700401 final int scrollX = mScrollX;
402 final int scrollY = mScrollY;
403 if (scrollX == 0 && scrollY == 0) {
404 buttonDrawable.draw(canvas);
405 } else {
406 canvas.translate(scrollX, scrollY);
407 buttonDrawable.draw(canvas);
408 canvas.translate(-scrollX, -scrollY);
409 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 }
411 }
412
413 @Override
414 protected int[] onCreateDrawableState(int extraSpace) {
415 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
416 if (isChecked()) {
417 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
418 }
419 return drawableState;
420 }
421
422 @Override
423 protected void drawableStateChanged() {
424 super.drawableStateChanged();
425
426 if (mButtonDrawable != null) {
427 int[] myDrawableState = getDrawableState();
428
429 // Set the state of the Drawable
430 mButtonDrawable.setState(myDrawableState);
431
432 invalidate();
433 }
434 }
435
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700436 @Override
Alan Viverette8de14942014-06-18 18:05:15 -0700437 public void drawableHotspotChanged(float x, float y) {
438 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700439
440 if (mButtonDrawable != null) {
441 mButtonDrawable.setHotspot(x, y);
442 }
443 }
444
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 @Override
446 protected boolean verifyDrawable(Drawable who) {
447 return super.verifyDrawable(who) || who == mButtonDrawable;
448 }
449
Dianne Hackborne2136772010-11-04 15:08:59 -0700450 @Override
451 public void jumpDrawablesToCurrentState() {
452 super.jumpDrawablesToCurrentState();
453 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
454 }
455
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 static class SavedState extends BaseSavedState {
457 boolean checked;
458
459 /**
460 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
461 */
462 SavedState(Parcelable superState) {
463 super(superState);
464 }
465
466 /**
467 * Constructor called from {@link #CREATOR}
468 */
469 private SavedState(Parcel in) {
470 super(in);
471 checked = (Boolean)in.readValue(null);
472 }
473
474 @Override
475 public void writeToParcel(Parcel out, int flags) {
476 super.writeToParcel(out, flags);
477 out.writeValue(checked);
478 }
479
480 @Override
481 public String toString() {
482 return "CompoundButton.SavedState{"
483 + Integer.toHexString(System.identityHashCode(this))
484 + " checked=" + checked + "}";
485 }
486
487 public static final Parcelable.Creator<SavedState> CREATOR
488 = new Parcelable.Creator<SavedState>() {
489 public SavedState createFromParcel(Parcel in) {
490 return new SavedState(in);
491 }
492
493 public SavedState[] newArray(int size) {
494 return new SavedState[size];
495 }
496 };
497 }
498
499 @Override
500 public Parcelable onSaveInstanceState() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 Parcelable superState = super.onSaveInstanceState();
502
503 SavedState ss = new SavedState(superState);
504
505 ss.checked = isChecked();
506 return ss;
507 }
508
509 @Override
510 public void onRestoreInstanceState(Parcelable state) {
511 SavedState ss = (SavedState) state;
512
513 super.onRestoreInstanceState(ss.getSuperState());
514 setChecked(ss.checked);
515 requestLayout();
516 }
517}