blob: 747d2b17bff3d1ebc28eae1b9806a14582972737 [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;
Alan Viverette91174362014-06-17 14:51:45 -070027import android.graphics.PorterDuff.Mode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.graphics.drawable.Drawable;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.util.AttributeSet;
32import android.view.Gravity;
Steve Zeigler7a367882010-02-23 16:39:08 -080033import android.view.ViewDebug;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070034import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov13774d22011-06-15 15:29:51 -070035import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036
37/**
38 * <p>
39 * A button with two states, checked and unchecked. When the button is pressed
40 * or clicked, the state changes automatically.
41 * </p>
42 *
43 * <p><strong>XML attributes</strong></p>
44 * <p>
45 * See {@link android.R.styleable#CompoundButton
46 * CompoundButton Attributes}, {@link android.R.styleable#Button Button
47 * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
48 * android.R.styleable#View View Attributes}
49 * </p>
50 */
51public abstract class CompoundButton extends Button implements Checkable {
52 private boolean mChecked;
53 private int mButtonResource;
54 private boolean mBroadcasting;
Alan Viverette91174362014-06-17 14:51:45 -070055
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 private Drawable mButtonDrawable;
Alan Viverette91174362014-06-17 14:51:45 -070057 private ColorStateList mButtonTint = null;
58 private PorterDuff.Mode mButtonTintMode = PorterDuff.Mode.SRC_ATOP;
59 private boolean mHasButtonTint = false;
60
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 Viverette91174362014-06-17 14:51:45 -070091 if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
92 mButtonTint = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
93 mButtonTintMode = Drawable.parseTintMode(a.getInt(
94 R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
95 mHasButtonTint = true;
96
97 applyButtonTint();
98 }
99
100 final boolean checked = a.getBoolean(
101 com.android.internal.R.styleable.CompoundButton_checked, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 setChecked(checked);
103
104 a.recycle();
105 }
106
107 public void toggle() {
108 setChecked(!mChecked);
109 }
110
111 @Override
112 public boolean performClick() {
113 /*
114 * XXX: These are tiny, need some surrounding 'expanded touch area',
115 * which will need to be implemented in Button if we only override
116 * performClick()
117 */
118
119 /* When clicked, toggle the state */
120 toggle();
121 return super.performClick();
122 }
123
Steve Zeigler7a367882010-02-23 16:39:08 -0800124 @ViewDebug.ExportedProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 public boolean isChecked() {
126 return mChecked;
127 }
128
129 /**
130 * <p>Changes the checked state of this button.</p>
131 *
132 * @param checked true to check the button, false to uncheck it
133 */
134 public void setChecked(boolean checked) {
135 if (mChecked != checked) {
136 mChecked = checked;
137 refreshDrawableState();
Alan Viverette77e9a282013-09-12 17:16:09 -0700138 notifyViewAccessibilityStateChangedIfNeeded(
139 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140
141 // Avoid infinite recursions if setChecked() is called from a listener
142 if (mBroadcasting) {
143 return;
144 }
145
146 mBroadcasting = true;
147 if (mOnCheckedChangeListener != null) {
148 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
149 }
150 if (mOnCheckedChangeWidgetListener != null) {
151 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
152 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700153
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 mBroadcasting = false;
155 }
156 }
157
158 /**
159 * Register a callback to be invoked when the checked state of this button
160 * changes.
161 *
162 * @param listener the callback to call on checked state change
163 */
164 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
165 mOnCheckedChangeListener = listener;
166 }
167
168 /**
169 * Register a callback to be invoked when the checked state of this button
170 * changes. This callback is used for internal purpose only.
171 *
172 * @param listener the callback to call on checked state change
173 * @hide
174 */
175 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
176 mOnCheckedChangeWidgetListener = listener;
177 }
178
179 /**
180 * Interface definition for a callback to be invoked when the checked state
181 * of a compound button changed.
182 */
183 public static interface OnCheckedChangeListener {
184 /**
185 * Called when the checked state of a compound button has changed.
186 *
187 * @param buttonView The compound button view whose state has changed.
188 * @param isChecked The new checked state of buttonView.
189 */
190 void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
191 }
192
193 /**
Alan Viverette91174362014-06-17 14:51:45 -0700194 * Set the button graphic to a given Drawable, identified by its resource
195 * id.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 *
Alan Viverette91174362014-06-17 14:51:45 -0700197 * @param resid the resource id of the drawable to use as the button
198 * graphic
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 */
200 public void setButtonDrawable(int resid) {
201 if (resid != 0 && resid == mButtonResource) {
202 return;
203 }
204
205 mButtonResource = resid;
206
207 Drawable d = null;
208 if (mButtonResource != 0) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800209 d = getContext().getDrawable(mButtonResource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 }
211 setButtonDrawable(d);
212 }
213
214 /**
Alan Viverette91174362014-06-17 14:51:45 -0700215 * Set the button graphic to a given Drawable
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 *
Alan Viverette91174362014-06-17 14:51:45 -0700217 * @param d The Drawable to use as the button graphic
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 */
219 public void setButtonDrawable(Drawable d) {
Alan Viverette91174362014-06-17 14:51:45 -0700220 if (mButtonDrawable != d) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 if (mButtonDrawable != null) {
222 mButtonDrawable.setCallback(null);
223 unscheduleDrawable(mButtonDrawable);
224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225
Alan Viverette91174362014-06-17 14:51:45 -0700226 mButtonDrawable = d;
227
228 if (d != null) {
229 d.setCallback(this);
230 d.setLayoutDirection(getLayoutDirection());
231 if (d.isStateful()) {
232 d.setState(getDrawableState());
233 }
234 d.setVisible(getVisibility() == VISIBLE, false);
235 setMinHeight(d.getIntrinsicHeight());
236 applyButtonTint();
237 }
238 }
239 }
240
241 /**
242 * Applies a tint to the button drawable.
243 * <p>
244 * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
245 * automatically mutate the drawable and apply the specified tint and tint
246 * mode using
247 * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
248 *
249 * @param tint the tint to apply, may be {@code null} to clear tint
250 * @param tintMode the blending mode used to apply the tint, may be
251 * {@code null} to clear tint
252 *
253 * @attr ref android.R.styleable#CompoundButton_buttonTint
254 * @attr ref android.R.styleable#CompoundButton_buttonTintMode
255 * @see Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)
256 */
257 private void setButtonTint(@Nullable ColorStateList tint,
258 @Nullable PorterDuff.Mode tintMode) {
259 mButtonTint = tint;
260 mButtonTintMode = tintMode;
261 mHasButtonTint = true;
262
263 applyButtonTint();
264 }
265
266 /**
267 * Applies a tint to the button drawable. Does not modify the current tint
268 * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default.
269 * <p>
270 * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
271 * automatically mutate the drawable and apply the specified tint and tint
272 * mode using
273 * {@link Drawable#setTint(ColorStateList, android.graphics.PorterDuff.Mode)}.
274 *
275 * @param tint the tint to apply, may be {@code null} to clear tint
276 *
277 * @attr ref android.R.styleable#CompoundButton_buttonTint
278 * @see #setButtonTint(ColorStateList, android.graphics.PorterDuff.Mode)
279 */
280 public void setButtonTint(@Nullable ColorStateList tint) {
281 setButtonTint(tint, mButtonTintMode);
282 }
283
284 /**
285 * @return the tint applied to the button drawable
286 * @attr ref android.R.styleable#CompoundButton_buttonTint
287 * @see #setButtonTint(ColorStateList, PorterDuff.Mode)
288 */
289 @Nullable
290 public ColorStateList getButtonTint() {
291 return mButtonTint;
292 }
293
294 /**
295 * Specifies the blending mode used to apply the tint specified by
296 * {@link #setButtonTint(ColorStateList)}} to the button drawable. The
297 * default mode is {@link PorterDuff.Mode#SRC_ATOP}.
298 *
299 * @param tintMode the blending mode used to apply the tint, may be
300 * {@code null} to clear tint
301 * @attr ref android.R.styleable#CompoundButton_buttonTintMode
302 * @see #setButtonTint(ColorStateList)
303 */
304 public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) {
305 setButtonTint(mButtonTint, tintMode);
306 }
307
308 /**
309 * @return the blending mode used to apply the tint to the button drawable
310 * @attr ref android.R.styleable#CompoundButton_buttonTintMode
311 * @see #setButtonTint(ColorStateList, PorterDuff.Mode)
312 */
313 @Nullable
314 public PorterDuff.Mode getButtonTintMode() {
315 return mButtonTintMode;
316 }
317
318 private void applyButtonTint() {
319 if (mButtonDrawable != null && mHasButtonTint) {
320 mButtonDrawable = mButtonDrawable.mutate();
321 mButtonDrawable.setTint(mButtonTint, mButtonTintMode);
322 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 }
324
325 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700326 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
327 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800328 event.setClassName(CompoundButton.class.getName());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700329 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700330 }
331
332 @Override
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700333 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
334 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800335 info.setClassName(CompoundButton.class.getName());
Svetoslav Ganov0f55cc32011-07-17 10:51:49 -0700336 info.setCheckable(true);
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700337 info.setChecked(mChecked);
338 }
339
340 @Override
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700341 public int getCompoundPaddingLeft() {
342 int padding = super.getCompoundPaddingLeft();
343 if (!isLayoutRtl()) {
344 final Drawable buttonDrawable = mButtonDrawable;
345 if (buttonDrawable != null) {
346 padding += buttonDrawable.getIntrinsicWidth();
347 }
348 }
349 return padding;
350 }
351
352 @Override
353 public int getCompoundPaddingRight() {
354 int padding = super.getCompoundPaddingRight();
355 if (isLayoutRtl()) {
356 final Drawable buttonDrawable = mButtonDrawable;
357 if (buttonDrawable != null) {
358 padding += buttonDrawable.getIntrinsicWidth();
359 }
360 }
361 return padding;
362 }
363
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -0800364 /**
365 * @hide
366 */
367 @Override
368 public int getHorizontalOffsetForDrawables() {
369 final Drawable buttonDrawable = mButtonDrawable;
370 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
371 }
372
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700373 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 protected void onDraw(Canvas canvas) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 final Drawable buttonDrawable = mButtonDrawable;
376 if (buttonDrawable != null) {
377 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700378 final int drawableHeight = buttonDrawable.getIntrinsicHeight();
379 final int drawableWidth = buttonDrawable.getIntrinsicWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800380
Alan Viverette61956602014-04-22 19:07:06 -0700381 final int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 switch (verticalGravity) {
383 case Gravity.BOTTOM:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700384 top = getHeight() - drawableHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 break;
386 case Gravity.CENTER_VERTICAL:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700387 top = (getHeight() - drawableHeight) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800388 break;
Alan Viverette61956602014-04-22 19:07:06 -0700389 default:
390 top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 }
Alan Viverette61956602014-04-22 19:07:06 -0700392 final int bottom = top + drawableHeight;
393 final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
394 final int right = isLayoutRtl() ? getWidth() : drawableWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800395
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700396 buttonDrawable.setBounds(left, top, right, bottom);
Alan Viverette61956602014-04-22 19:07:06 -0700397
398 final Drawable background = getBackground();
Alan Viverettec80ad992014-05-19 15:46:17 -0700399 if (background != null) {
Alan Viverette61956602014-04-22 19:07:06 -0700400 background.setHotspotBounds(left, top, right, bottom);
401 }
402 }
403
404 super.onDraw(canvas);
405
406 if (buttonDrawable != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800407 buttonDrawable.draw(canvas);
408 }
409 }
410
411 @Override
412 protected int[] onCreateDrawableState(int extraSpace) {
413 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
414 if (isChecked()) {
415 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
416 }
417 return drawableState;
418 }
419
420 @Override
421 protected void drawableStateChanged() {
422 super.drawableStateChanged();
423
424 if (mButtonDrawable != null) {
425 int[] myDrawableState = getDrawableState();
426
427 // Set the state of the Drawable
428 mButtonDrawable.setState(myDrawableState);
429
430 invalidate();
431 }
432 }
433
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700434 /** @hide */
435 @Override
436 protected void setDrawableHotspot(float x, float y) {
437 super.setDrawableHotspot(x, y);
438
439 if (mButtonDrawable != null) {
440 mButtonDrawable.setHotspot(x, y);
441 }
442 }
443
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800444 @Override
445 protected boolean verifyDrawable(Drawable who) {
446 return super.verifyDrawable(who) || who == mButtonDrawable;
447 }
448
Dianne Hackborne2136772010-11-04 15:08:59 -0700449 @Override
450 public void jumpDrawablesToCurrentState() {
451 super.jumpDrawablesToCurrentState();
452 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
453 }
454
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 static class SavedState extends BaseSavedState {
456 boolean checked;
457
458 /**
459 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
460 */
461 SavedState(Parcelable superState) {
462 super(superState);
463 }
464
465 /**
466 * Constructor called from {@link #CREATOR}
467 */
468 private SavedState(Parcel in) {
469 super(in);
470 checked = (Boolean)in.readValue(null);
471 }
472
473 @Override
474 public void writeToParcel(Parcel out, int flags) {
475 super.writeToParcel(out, flags);
476 out.writeValue(checked);
477 }
478
479 @Override
480 public String toString() {
481 return "CompoundButton.SavedState{"
482 + Integer.toHexString(System.identityHashCode(this))
483 + " checked=" + checked + "}";
484 }
485
486 public static final Parcelable.Creator<SavedState> CREATOR
487 = new Parcelable.Creator<SavedState>() {
488 public SavedState createFromParcel(Parcel in) {
489 return new SavedState(in);
490 }
491
492 public SavedState[] newArray(int size) {
493 return new SavedState[size];
494 }
495 };
496 }
497
498 @Override
499 public Parcelable onSaveInstanceState() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 Parcelable superState = super.onSaveInstanceState();
501
502 SavedState ss = new SavedState(superState);
503
504 ss.checked = isChecked();
505 return ss;
506 }
507
508 @Override
509 public void onRestoreInstanceState(Parcelable state) {
510 SavedState ss = (SavedState) state;
511
512 super.onRestoreInstanceState(ss.getSuperState());
513 setChecked(ss.checked);
514 requestLayout();
515 }
516}