blob: 6aff4f4f2177306a6d7fe327044ea23538d4a1f3 [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
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;
Steve Zeigler7a367882010-02-23 16:39:08 -080029import android.view.ViewDebug;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -070030import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov13774d22011-06-15 15:29:51 -070031import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032
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
Alan Viverette617feb92013-09-09 18:09:13 -070067 public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) {
68 this(context, attrs, defStyleAttr, 0);
69 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
Alan Viverette617feb92013-09-09 18:09:13 -070071 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076
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
Steve Zeigler7a367882010-02-23 16:39:08 -0800106 @ViewDebug.ExportedProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 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();
Alan Viverette77e9a282013-09-12 17:16:09 -0700120 notifyViewAccessibilityStateChangedIfNeeded(
121 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122
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 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700135
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800136 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) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800189 d = getContext().getDrawable(mButtonResource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800190 }
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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 d.setVisible(getVisibility() == VISIBLE, false);
207 mButtonDrawable = d;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 setMinHeight(mButtonDrawable.getIntrinsicHeight());
209 }
210
211 refreshDrawableState();
212 }
213
214 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700215 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
216 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800217 event.setClassName(CompoundButton.class.getName());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700218 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700219 }
220
221 @Override
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700222 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
223 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800224 info.setClassName(CompoundButton.class.getName());
Svetoslav Ganov0f55cc32011-07-17 10:51:49 -0700225 info.setCheckable(true);
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700226 info.setChecked(mChecked);
227 }
228
229 @Override
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700230 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
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -0800253 /**
254 * @hide
255 */
256 @Override
257 public int getHorizontalOffsetForDrawables() {
258 final Drawable buttonDrawable = mButtonDrawable;
259 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
260 }
261
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700262 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 protected void onDraw(Canvas canvas) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 final Drawable buttonDrawable = mButtonDrawable;
265 if (buttonDrawable != null) {
266 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700267 final int drawableHeight = buttonDrawable.getIntrinsicHeight();
268 final int drawableWidth = buttonDrawable.getIntrinsicWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269
Alan Viverette61956602014-04-22 19:07:06 -0700270 final int top;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 switch (verticalGravity) {
272 case Gravity.BOTTOM:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700273 top = getHeight() - drawableHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 break;
275 case Gravity.CENTER_VERTICAL:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700276 top = (getHeight() - drawableHeight) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 break;
Alan Viverette61956602014-04-22 19:07:06 -0700278 default:
279 top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 }
Alan Viverette61956602014-04-22 19:07:06 -0700281 final int bottom = top + drawableHeight;
282 final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
283 final int right = isLayoutRtl() ? getWidth() : drawableWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700285 buttonDrawable.setBounds(left, top, right, bottom);
Alan Viverette61956602014-04-22 19:07:06 -0700286
287 final Drawable background = getBackground();
Alan Viverettec80ad992014-05-19 15:46:17 -0700288 if (background != null) {
Alan Viverette61956602014-04-22 19:07:06 -0700289 background.setHotspotBounds(left, top, right, bottom);
290 }
291 }
292
293 super.onDraw(canvas);
294
295 if (buttonDrawable != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 buttonDrawable.draw(canvas);
297 }
298 }
299
300 @Override
301 protected int[] onCreateDrawableState(int extraSpace) {
302 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
303 if (isChecked()) {
304 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
305 }
306 return drawableState;
307 }
308
309 @Override
310 protected void drawableStateChanged() {
311 super.drawableStateChanged();
312
313 if (mButtonDrawable != null) {
314 int[] myDrawableState = getDrawableState();
315
316 // Set the state of the Drawable
317 mButtonDrawable.setState(myDrawableState);
318
319 invalidate();
320 }
321 }
322
323 @Override
324 protected boolean verifyDrawable(Drawable who) {
325 return super.verifyDrawable(who) || who == mButtonDrawable;
326 }
327
Dianne Hackborne2136772010-11-04 15:08:59 -0700328 @Override
329 public void jumpDrawablesToCurrentState() {
330 super.jumpDrawablesToCurrentState();
331 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
332 }
333
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800334 static class SavedState extends BaseSavedState {
335 boolean checked;
336
337 /**
338 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
339 */
340 SavedState(Parcelable superState) {
341 super(superState);
342 }
343
344 /**
345 * Constructor called from {@link #CREATOR}
346 */
347 private SavedState(Parcel in) {
348 super(in);
349 checked = (Boolean)in.readValue(null);
350 }
351
352 @Override
353 public void writeToParcel(Parcel out, int flags) {
354 super.writeToParcel(out, flags);
355 out.writeValue(checked);
356 }
357
358 @Override
359 public String toString() {
360 return "CompoundButton.SavedState{"
361 + Integer.toHexString(System.identityHashCode(this))
362 + " checked=" + checked + "}";
363 }
364
365 public static final Parcelable.Creator<SavedState> CREATOR
366 = new Parcelable.Creator<SavedState>() {
367 public SavedState createFromParcel(Parcel in) {
368 return new SavedState(in);
369 }
370
371 public SavedState[] newArray(int size) {
372 return new SavedState[size];
373 }
374 };
375 }
376
377 @Override
378 public Parcelable onSaveInstanceState() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 Parcelable superState = super.onSaveInstanceState();
380
381 SavedState ss = new SavedState(superState);
382
383 ss.checked = isChecked();
384 return ss;
385 }
386
387 @Override
388 public void onRestoreInstanceState(Parcelable state) {
389 SavedState ss = (SavedState) state;
390
391 super.onRestoreInstanceState(ss.getSuperState());
392 setChecked(ss.checked);
393 requestLayout();
394 }
395}