blob: 082ff3d46906272bb88058514c7bbe63d067dd47 [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
67 public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
68 super(context, attrs, defStyle);
69
70 TypedArray a =
71 context.obtainStyledAttributes(
72 attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);
73
74 Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
75 if (d != null) {
76 setButtonDrawable(d);
77 }
78
79 boolean checked = a
80 .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
81 setChecked(checked);
82
83 a.recycle();
84 }
85
86 public void toggle() {
87 setChecked(!mChecked);
88 }
89
90 @Override
91 public boolean performClick() {
92 /*
93 * XXX: These are tiny, need some surrounding 'expanded touch area',
94 * which will need to be implemented in Button if we only override
95 * performClick()
96 */
97
98 /* When clicked, toggle the state */
99 toggle();
100 return super.performClick();
101 }
102
Steve Zeigler7a367882010-02-23 16:39:08 -0800103 @ViewDebug.ExportedProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 public boolean isChecked() {
105 return mChecked;
106 }
107
108 /**
109 * <p>Changes the checked state of this button.</p>
110 *
111 * @param checked true to check the button, false to uncheck it
112 */
113 public void setChecked(boolean checked) {
114 if (mChecked != checked) {
115 mChecked = checked;
116 refreshDrawableState();
Alan Viverette77e9a282013-09-12 17:16:09 -0700117 notifyViewAccessibilityStateChangedIfNeeded(
118 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119
120 // Avoid infinite recursions if setChecked() is called from a listener
121 if (mBroadcasting) {
122 return;
123 }
124
125 mBroadcasting = true;
126 if (mOnCheckedChangeListener != null) {
127 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
128 }
129 if (mOnCheckedChangeWidgetListener != null) {
130 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
131 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700132
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 mBroadcasting = false;
134 }
135 }
136
137 /**
138 * Register a callback to be invoked when the checked state of this button
139 * changes.
140 *
141 * @param listener the callback to call on checked state change
142 */
143 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
144 mOnCheckedChangeListener = listener;
145 }
146
147 /**
148 * Register a callback to be invoked when the checked state of this button
149 * changes. This callback is used for internal purpose only.
150 *
151 * @param listener the callback to call on checked state change
152 * @hide
153 */
154 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
155 mOnCheckedChangeWidgetListener = listener;
156 }
157
158 /**
159 * Interface definition for a callback to be invoked when the checked state
160 * of a compound button changed.
161 */
162 public static interface OnCheckedChangeListener {
163 /**
164 * Called when the checked state of a compound button has changed.
165 *
166 * @param buttonView The compound button view whose state has changed.
167 * @param isChecked The new checked state of buttonView.
168 */
169 void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
170 }
171
172 /**
173 * Set the background to a given Drawable, identified by its resource id.
174 *
175 * @param resid the resource id of the drawable to use as the background
176 */
177 public void setButtonDrawable(int resid) {
178 if (resid != 0 && resid == mButtonResource) {
179 return;
180 }
181
182 mButtonResource = resid;
183
184 Drawable d = null;
185 if (mButtonResource != 0) {
186 d = getResources().getDrawable(mButtonResource);
187 }
188 setButtonDrawable(d);
189 }
190
191 /**
192 * Set the background to a given Drawable
193 *
194 * @param d The Drawable to use as the background
195 */
196 public void setButtonDrawable(Drawable d) {
197 if (d != null) {
198 if (mButtonDrawable != null) {
199 mButtonDrawable.setCallback(null);
200 unscheduleDrawable(mButtonDrawable);
201 }
202 d.setCallback(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 d.setVisible(getVisibility() == VISIBLE, false);
204 mButtonDrawable = d;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 setMinHeight(mButtonDrawable.getIntrinsicHeight());
206 }
207
208 refreshDrawableState();
209 }
210
211 @Override
Svetoslav Ganov30401322011-05-12 18:53:45 -0700212 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
213 super.onInitializeAccessibilityEvent(event);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800214 event.setClassName(CompoundButton.class.getName());
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700215 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700216 }
217
218 @Override
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700219 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
220 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800221 info.setClassName(CompoundButton.class.getName());
Svetoslav Ganov0f55cc32011-07-17 10:51:49 -0700222 info.setCheckable(true);
Svetoslav Ganov13774d22011-06-15 15:29:51 -0700223 info.setChecked(mChecked);
224 }
225
226 @Override
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700227 public int getCompoundPaddingLeft() {
228 int padding = super.getCompoundPaddingLeft();
229 if (!isLayoutRtl()) {
230 final Drawable buttonDrawable = mButtonDrawable;
231 if (buttonDrawable != null) {
232 padding += buttonDrawable.getIntrinsicWidth();
233 }
234 }
235 return padding;
236 }
237
238 @Override
239 public int getCompoundPaddingRight() {
240 int padding = super.getCompoundPaddingRight();
241 if (isLayoutRtl()) {
242 final Drawable buttonDrawable = mButtonDrawable;
243 if (buttonDrawable != null) {
244 padding += buttonDrawable.getIntrinsicWidth();
245 }
246 }
247 return padding;
248 }
249
Fabrice Di Megliob878ddb2012-11-27 17:44:33 -0800250 /**
251 * @hide
252 */
253 @Override
254 public int getHorizontalOffsetForDrawables() {
255 final Drawable buttonDrawable = mButtonDrawable;
256 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
257 }
258
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700259 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 protected void onDraw(Canvas canvas) {
261 super.onDraw(canvas);
262
263 final Drawable buttonDrawable = mButtonDrawable;
264 if (buttonDrawable != null) {
265 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700266 final int drawableHeight = buttonDrawable.getIntrinsicHeight();
267 final int drawableWidth = buttonDrawable.getIntrinsicWidth();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700269 int top = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 switch (verticalGravity) {
271 case Gravity.BOTTOM:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700272 top = getHeight() - drawableHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 break;
274 case Gravity.CENTER_VERTICAL:
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700275 top = (getHeight() - drawableHeight) / 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 break;
277 }
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700278 int bottom = top + drawableHeight;
279 int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
280 int right = isLayoutRtl() ? getWidth() : drawableWidth;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281
Fabrice Di Meglio28426792012-06-05 16:41:18 -0700282 buttonDrawable.setBounds(left, top, right, bottom);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 buttonDrawable.draw(canvas);
284 }
285 }
286
287 @Override
288 protected int[] onCreateDrawableState(int extraSpace) {
289 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
290 if (isChecked()) {
291 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
292 }
293 return drawableState;
294 }
295
296 @Override
297 protected void drawableStateChanged() {
298 super.drawableStateChanged();
299
300 if (mButtonDrawable != null) {
301 int[] myDrawableState = getDrawableState();
302
303 // Set the state of the Drawable
304 mButtonDrawable.setState(myDrawableState);
305
306 invalidate();
307 }
308 }
309
310 @Override
311 protected boolean verifyDrawable(Drawable who) {
312 return super.verifyDrawable(who) || who == mButtonDrawable;
313 }
314
Dianne Hackborne2136772010-11-04 15:08:59 -0700315 @Override
316 public void jumpDrawablesToCurrentState() {
317 super.jumpDrawablesToCurrentState();
318 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
319 }
320
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 static class SavedState extends BaseSavedState {
322 boolean checked;
323
324 /**
325 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
326 */
327 SavedState(Parcelable superState) {
328 super(superState);
329 }
330
331 /**
332 * Constructor called from {@link #CREATOR}
333 */
334 private SavedState(Parcel in) {
335 super(in);
336 checked = (Boolean)in.readValue(null);
337 }
338
339 @Override
340 public void writeToParcel(Parcel out, int flags) {
341 super.writeToParcel(out, flags);
342 out.writeValue(checked);
343 }
344
345 @Override
346 public String toString() {
347 return "CompoundButton.SavedState{"
348 + Integer.toHexString(System.identityHashCode(this))
349 + " checked=" + checked + "}";
350 }
351
352 public static final Parcelable.Creator<SavedState> CREATOR
353 = new Parcelable.Creator<SavedState>() {
354 public SavedState createFromParcel(Parcel in) {
355 return new SavedState(in);
356 }
357
358 public SavedState[] newArray(int size) {
359 return new SavedState[size];
360 }
361 };
362 }
363
364 @Override
365 public Parcelable onSaveInstanceState() {
366 // Force our ancestor class to save its state
367 setFreezesText(true);
368 Parcelable superState = super.onSaveInstanceState();
369
370 SavedState ss = new SavedState(superState);
371
372 ss.checked = isChecked();
373 return ss;
374 }
375
376 @Override
377 public void onRestoreInstanceState(Parcelable state) {
378 SavedState ss = (SavedState) state;
379
380 super.onRestoreInstanceState(ss.getSuperState());
381 setChecked(ss.checked);
382 requestLayout();
383 }
384}