blob: da94082afa3f03701ecaaac4eb68d8f66ae6cdbd [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();
Svetoslav6254f482013-06-04 17:22:14 -0700120 notifyViewAccessibilityStateChangedIfNeeded();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800121
122 // Avoid infinite recursions if setChecked() is called from a listener
123 if (mBroadcasting) {
124 return;
125 }
126
127 mBroadcasting = true;
128 if (mOnCheckedChangeListener != null) {
129 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
130 }
131 if (mOnCheckedChangeWidgetListener != null) {
132 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
133 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700134
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 mBroadcasting = false;
136 }
137 }
138
139 /**
140 * Register a callback to be invoked when the checked state of this button
141 * changes.
142 *
143 * @param listener the callback to call on checked state change
144 */
145 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
146 mOnCheckedChangeListener = listener;
147 }
148
149 /**
150 * Register a callback to be invoked when the checked state of this button
151 * changes. This callback is used for internal purpose only.
152 *
153 * @param listener the callback to call on checked state change
154 * @hide
155 */
156 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
157 mOnCheckedChangeWidgetListener = listener;
158 }
159
160 /**
161 * Interface definition for a callback to be invoked when the checked state
162 * of a compound button changed.
163 */
164 public static interface OnCheckedChangeListener {
165 /**
166 * Called when the checked state of a compound button has changed.
167 *
168 * @param buttonView The compound button view whose state has changed.
169 * @param isChecked The new checked state of buttonView.
170 */
171 void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
172 }
173
174 /**
175 * Set the background to a given Drawable, identified by its resource id.
176 *
177 * @param resid the resource id of the drawable to use as the background
178 */
179 public void setButtonDrawable(int resid) {
180 if (resid != 0 && resid == mButtonResource) {
181 return;
182 }
183
184 mButtonResource = resid;
185
186 Drawable d = null;
187 if (mButtonResource != 0) {
188 d = getResources().getDrawable(mButtonResource);
189 }
190 setButtonDrawable(d);
191 }
192
193 /**
194 * Set the background to a given Drawable
195 *
196 * @param d The Drawable to use as the background
197 */
198 public void setButtonDrawable(Drawable d) {
199 if (d != null) {
200 if (mButtonDrawable != null) {
201 mButtonDrawable.setCallback(null);
202 unscheduleDrawable(mButtonDrawable);
203 }
204 d.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 d.setVisible(getVisibility() == VISIBLE, false);
206 mButtonDrawable = d;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800207 setMinHeight(mButtonDrawable.getIntrinsicHeight());
208 }
209
210 refreshDrawableState();
211 }
212
213 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700214 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
215 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800216 event.setClassName(CompoundButton.class.getName());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700217 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700218 }
219
220 @Override
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700221 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
222 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800223 info.setClassName(CompoundButton.class.getName());
Svetoslav Ganov0f55cc32011-07-17 10:51:49 -0700224 info.setCheckable(true);
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700225 info.setChecked(mChecked);
226 }
227
228 @Override
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700229 public int getCompoundPaddingLeft() {
230 int padding = super.getCompoundPaddingLeft();
231 if (!isLayoutRtl()) {
232 final Drawable buttonDrawable = mButtonDrawable;
233 if (buttonDrawable != null) {
234 padding += buttonDrawable.getIntrinsicWidth();
235 }
236 }
237 return padding;
238 }
239
240 @Override
241 public int getCompoundPaddingRight() {
242 int padding = super.getCompoundPaddingRight();
243 if (isLayoutRtl()) {
244 final Drawable buttonDrawable = mButtonDrawable;
245 if (buttonDrawable != null) {
246 padding += buttonDrawable.getIntrinsicWidth();
247 }
248 }
249 return padding;
250 }
251
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -0800252 /**
253 * @hide
254 */
255 @Override
256 public int getHorizontalOffsetForDrawables() {
257 final Drawable buttonDrawable = mButtonDrawable;
258 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
259 }
260
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700261 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 protected void onDraw(Canvas canvas) {
263 super.onDraw(canvas);
264
265 final Drawable buttonDrawable = mButtonDrawable;
266 if (buttonDrawable != null) {
267 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700268 final int drawableHeight = buttonDrawable.getIntrinsicHeight();
269 final int drawableWidth = buttonDrawable.getIntrinsicWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700271 int top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 switch (verticalGravity) {
273 case Gravity.BOTTOM:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700274 top = getHeight() - drawableHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 break;
276 case Gravity.CENTER_VERTICAL:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700277 top = (getHeight() - drawableHeight) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 break;
279 }
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700280 int bottom = top + drawableHeight;
281 int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
282 int right = isLayoutRtl() ? getWidth() : drawableWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700284 buttonDrawable.setBounds(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 buttonDrawable.draw(canvas);
286 }
287 }
288
289 @Override
290 protected int[] onCreateDrawableState(int extraSpace) {
291 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
292 if (isChecked()) {
293 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
294 }
295 return drawableState;
296 }
297
298 @Override
299 protected void drawableStateChanged() {
300 super.drawableStateChanged();
301
302 if (mButtonDrawable != null) {
303 int[] myDrawableState = getDrawableState();
304
305 // Set the state of the Drawable
306 mButtonDrawable.setState(myDrawableState);
307
308 invalidate();
309 }
310 }
311
312 @Override
313 protected boolean verifyDrawable(Drawable who) {
314 return super.verifyDrawable(who) || who == mButtonDrawable;
315 }
316
Dianne Hackborne2136772010-11-04 15:08:59 -0700317 @Override
318 public void jumpDrawablesToCurrentState() {
319 super.jumpDrawablesToCurrentState();
320 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
321 }
322
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 static class SavedState extends BaseSavedState {
324 boolean checked;
325
326 /**
327 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
328 */
329 SavedState(Parcelable superState) {
330 super(superState);
331 }
332
333 /**
334 * Constructor called from {@link #CREATOR}
335 */
336 private SavedState(Parcel in) {
337 super(in);
338 checked = (Boolean)in.readValue(null);
339 }
340
341 @Override
342 public void writeToParcel(Parcel out, int flags) {
343 super.writeToParcel(out, flags);
344 out.writeValue(checked);
345 }
346
347 @Override
348 public String toString() {
349 return "CompoundButton.SavedState{"
350 + Integer.toHexString(System.identityHashCode(this))
351 + " checked=" + checked + "}";
352 }
353
354 public static final Parcelable.Creator<SavedState> CREATOR
355 = new Parcelable.Creator<SavedState>() {
356 public SavedState createFromParcel(Parcel in) {
357 return new SavedState(in);
358 }
359
360 public SavedState[] newArray(int size) {
361 return new SavedState[size];
362 }
363 };
364 }
365
366 @Override
367 public Parcelable onSaveInstanceState() {
368 // Force our ancestor class to save its state
369 setFreezesText(true);
370 Parcelable superState = super.onSaveInstanceState();
371
372 SavedState ss = new SavedState(superState);
373
374 ss.checked = isChecked();
375 return ss;
376 }
377
378 @Override
379 public void onRestoreInstanceState(Parcelable state) {
380 SavedState ss = (SavedState) state;
381
382 super.onRestoreInstanceState(ss.getSuperState());
383 setChecked(ss.checked);
384 requestLayout();
385 }
386}