blob: f050d41e3c55f449eb09d38357b3739ce362faf6 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32/**
33 * <p>
34 * A button with two states, checked and unchecked. When the button is pressed
35 * or clicked, the state changes automatically.
36 * </p>
37 *
38 * <p><strong>XML attributes</strong></p>
39 * <p>
40 * See {@link android.R.styleable#CompoundButton
41 * CompoundButton Attributes}, {@link android.R.styleable#Button Button
42 * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
43 * android.R.styleable#View View Attributes}
44 * </p>
45 */
46public abstract class CompoundButton extends Button implements Checkable {
47 private boolean mChecked;
48 private int mButtonResource;
49 private boolean mBroadcasting;
50 private Drawable mButtonDrawable;
51 private OnCheckedChangeListener mOnCheckedChangeListener;
52 private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
53
54 private static final int[] CHECKED_STATE_SET = {
55 R.attr.state_checked
56 };
57
58 public CompoundButton(Context context) {
59 this(context, null);
60 }
61
62 public CompoundButton(Context context, AttributeSet attrs) {
63 this(context, attrs, 0);
64 }
65
66 public CompoundButton(Context context, AttributeSet attrs, int defStyle) {
67 super(context, attrs, defStyle);
68
69 TypedArray a =
70 context.obtainStyledAttributes(
71 attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0);
72
73 Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
74 if (d != null) {
75 setButtonDrawable(d);
76 }
77
78 boolean checked = a
79 .getBoolean(com.android.internal.R.styleable.CompoundButton_checked, false);
80 setChecked(checked);
81
82 a.recycle();
83 }
84
85 public void toggle() {
86 setChecked(!mChecked);
87 }
88
89 @Override
90 public boolean performClick() {
91 /*
92 * XXX: These are tiny, need some surrounding 'expanded touch area',
93 * which will need to be implemented in Button if we only override
94 * performClick()
95 */
96
97 /* When clicked, toggle the state */
98 toggle();
99 return super.performClick();
100 }
101
Steve Zeigler7a367882010-02-23 16:39:08 -0800102 @ViewDebug.ExportedProperty
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 public boolean isChecked() {
104 return mChecked;
105 }
106
107 /**
108 * <p>Changes the checked state of this button.</p>
109 *
110 * @param checked true to check the button, false to uncheck it
111 */
112 public void setChecked(boolean checked) {
113 if (mChecked != checked) {
114 mChecked = checked;
115 refreshDrawableState();
116
117 // Avoid infinite recursions if setChecked() is called from a listener
118 if (mBroadcasting) {
119 return;
120 }
121
122 mBroadcasting = true;
123 if (mOnCheckedChangeListener != null) {
124 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
125 }
126 if (mOnCheckedChangeWidgetListener != null) {
127 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
128 }
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700129
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 mBroadcasting = false;
131 }
132 }
133
134 /**
135 * Register a callback to be invoked when the checked state of this button
136 * changes.
137 *
138 * @param listener the callback to call on checked state change
139 */
140 public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
141 mOnCheckedChangeListener = listener;
142 }
143
144 /**
145 * Register a callback to be invoked when the checked state of this button
146 * changes. This callback is used for internal purpose only.
147 *
148 * @param listener the callback to call on checked state change
149 * @hide
150 */
151 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
152 mOnCheckedChangeWidgetListener = listener;
153 }
154
155 /**
156 * Interface definition for a callback to be invoked when the checked state
157 * of a compound button changed.
158 */
159 public static interface OnCheckedChangeListener {
160 /**
161 * Called when the checked state of a compound button has changed.
162 *
163 * @param buttonView The compound button view whose state has changed.
164 * @param isChecked The new checked state of buttonView.
165 */
166 void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
167 }
168
169 /**
170 * Set the background to a given Drawable, identified by its resource id.
171 *
172 * @param resid the resource id of the drawable to use as the background
173 */
174 public void setButtonDrawable(int resid) {
175 if (resid != 0 && resid == mButtonResource) {
176 return;
177 }
178
179 mButtonResource = resid;
180
181 Drawable d = null;
182 if (mButtonResource != 0) {
183 d = getResources().getDrawable(mButtonResource);
184 }
185 setButtonDrawable(d);
186 }
187
188 /**
189 * Set the background to a given Drawable
190 *
191 * @param d The Drawable to use as the background
192 */
193 public void setButtonDrawable(Drawable d) {
194 if (d != null) {
195 if (mButtonDrawable != null) {
196 mButtonDrawable.setCallback(null);
197 unscheduleDrawable(mButtonDrawable);
198 }
199 d.setCallback(this);
200 d.setState(getDrawableState());
201 d.setVisible(getVisibility() == VISIBLE, false);
202 mButtonDrawable = d;
203 mButtonDrawable.setState(null);
204 setMinHeight(mButtonDrawable.getIntrinsicHeight());
205 }
206
207 refreshDrawableState();
208 }
209
210 @Override
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700211 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
212 super.onPopulateAccessibilityEvent(event);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700213
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700214 int resourceId = 0;
215 if (mChecked) {
216 resourceId = R.string.accessibility_compound_button_selected;
217 } else {
218 resourceId = R.string.accessibility_compound_button_unselected;
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700219 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700220 String state = getResources().getString(resourceId);
221 event.getText().add(state);
222 event.setChecked(mChecked);
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700223 }
224
225 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 protected void onDraw(Canvas canvas) {
227 super.onDraw(canvas);
228
229 final Drawable buttonDrawable = mButtonDrawable;
230 if (buttonDrawable != null) {
231 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
232 final int height = buttonDrawable.getIntrinsicHeight();
233
234 int y = 0;
235
236 switch (verticalGravity) {
237 case Gravity.BOTTOM:
238 y = getHeight() - height;
239 break;
240 case Gravity.CENTER_VERTICAL:
241 y = (getHeight() - height) / 2;
242 break;
243 }
244
245 buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
246 buttonDrawable.draw(canvas);
247 }
248 }
249
250 @Override
251 protected int[] onCreateDrawableState(int extraSpace) {
252 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
253 if (isChecked()) {
254 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
255 }
256 return drawableState;
257 }
258
259 @Override
260 protected void drawableStateChanged() {
261 super.drawableStateChanged();
262
263 if (mButtonDrawable != null) {
264 int[] myDrawableState = getDrawableState();
265
266 // Set the state of the Drawable
267 mButtonDrawable.setState(myDrawableState);
268
269 invalidate();
270 }
271 }
272
273 @Override
274 protected boolean verifyDrawable(Drawable who) {
275 return super.verifyDrawable(who) || who == mButtonDrawable;
276 }
277
Dianne Hackborne2136772010-11-04 15:08:59 -0700278 @Override
279 public void jumpDrawablesToCurrentState() {
280 super.jumpDrawablesToCurrentState();
281 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
282 }
283
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 static class SavedState extends BaseSavedState {
285 boolean checked;
286
287 /**
288 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
289 */
290 SavedState(Parcelable superState) {
291 super(superState);
292 }
293
294 /**
295 * Constructor called from {@link #CREATOR}
296 */
297 private SavedState(Parcel in) {
298 super(in);
299 checked = (Boolean)in.readValue(null);
300 }
301
302 @Override
303 public void writeToParcel(Parcel out, int flags) {
304 super.writeToParcel(out, flags);
305 out.writeValue(checked);
306 }
307
308 @Override
309 public String toString() {
310 return "CompoundButton.SavedState{"
311 + Integer.toHexString(System.identityHashCode(this))
312 + " checked=" + checked + "}";
313 }
314
315 public static final Parcelable.Creator<SavedState> CREATOR
316 = new Parcelable.Creator<SavedState>() {
317 public SavedState createFromParcel(Parcel in) {
318 return new SavedState(in);
319 }
320
321 public SavedState[] newArray(int size) {
322 return new SavedState[size];
323 }
324 };
325 }
326
327 @Override
328 public Parcelable onSaveInstanceState() {
329 // Force our ancestor class to save its state
330 setFreezesText(true);
331 Parcelable superState = super.onSaveInstanceState();
332
333 SavedState ss = new SavedState(superState);
334
335 ss.checked = isChecked();
336 return ss;
337 }
338
339 @Override
340 public void onRestoreInstanceState(Parcelable state) {
341 SavedState ss = (SavedState) state;
342
343 super.onRestoreInstanceState(ss.getSuperState());
344 setChecked(ss.checked);
345 requestLayout();
346 }
347}