blob: 0df45cca6a65a606b5fc70b35996d79108e588fb [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
Jean-Baptiste Querucf4550c2009-07-21 11:16:54 -0700211 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
212 boolean populated = super.dispatchPopulateAccessibilityEvent(event);
213
214 if (!populated) {
215 int resourceId = 0;
216 if (mChecked) {
217 resourceId = R.string.accessibility_compound_button_selected;
218 } else {
219 resourceId = R.string.accessibility_compound_button_unselected;
220 }
221 String state = getResources().getString(resourceId);
222 event.getText().add(state);
223 event.setChecked(mChecked);
224 }
225
226 return populated;
227 }
228
229 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 protected void onDraw(Canvas canvas) {
231 super.onDraw(canvas);
232
233 final Drawable buttonDrawable = mButtonDrawable;
234 if (buttonDrawable != null) {
235 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
236 final int height = buttonDrawable.getIntrinsicHeight();
237
238 int y = 0;
239
240 switch (verticalGravity) {
241 case Gravity.BOTTOM:
242 y = getHeight() - height;
243 break;
244 case Gravity.CENTER_VERTICAL:
245 y = (getHeight() - height) / 2;
246 break;
247 }
248
249 buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
250 buttonDrawable.draw(canvas);
251 }
252 }
253
254 @Override
255 protected int[] onCreateDrawableState(int extraSpace) {
256 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
257 if (isChecked()) {
258 mergeDrawableStates(drawableState, CHECKED_STATE_SET);
259 }
260 return drawableState;
261 }
262
263 @Override
264 protected void drawableStateChanged() {
265 super.drawableStateChanged();
266
267 if (mButtonDrawable != null) {
268 int[] myDrawableState = getDrawableState();
269
270 // Set the state of the Drawable
271 mButtonDrawable.setState(myDrawableState);
272
273 invalidate();
274 }
275 }
276
277 @Override
278 protected boolean verifyDrawable(Drawable who) {
279 return super.verifyDrawable(who) || who == mButtonDrawable;
280 }
281
Dianne Hackborne2136772010-11-04 15:08:59 -0700282 @Override
283 public void jumpDrawablesToCurrentState() {
284 super.jumpDrawablesToCurrentState();
285 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
286 }
287
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 static class SavedState extends BaseSavedState {
289 boolean checked;
290
291 /**
292 * Constructor called from {@link CompoundButton#onSaveInstanceState()}
293 */
294 SavedState(Parcelable superState) {
295 super(superState);
296 }
297
298 /**
299 * Constructor called from {@link #CREATOR}
300 */
301 private SavedState(Parcel in) {
302 super(in);
303 checked = (Boolean)in.readValue(null);
304 }
305
306 @Override
307 public void writeToParcel(Parcel out, int flags) {
308 super.writeToParcel(out, flags);
309 out.writeValue(checked);
310 }
311
312 @Override
313 public String toString() {
314 return "CompoundButton.SavedState{"
315 + Integer.toHexString(System.identityHashCode(this))
316 + " checked=" + checked + "}";
317 }
318
319 public static final Parcelable.Creator<SavedState> CREATOR
320 = new Parcelable.Creator<SavedState>() {
321 public SavedState createFromParcel(Parcel in) {
322 return new SavedState(in);
323 }
324
325 public SavedState[] newArray(int size) {
326 return new SavedState[size];
327 }
328 };
329 }
330
331 @Override
332 public Parcelable onSaveInstanceState() {
333 // Force our ancestor class to save its state
334 setFreezesText(true);
335 Parcelable superState = super.onSaveInstanceState();
336
337 SavedState ss = new SavedState(superState);
338
339 ss.checked = isChecked();
340 return ss;
341 }
342
343 @Override
344 public void onRestoreInstanceState(Parcelable state) {
345 SavedState ss = (SavedState) state;
346
347 super.onRestoreInstanceState(ss.getSuperState());
348 setChecked(ss.checked);
349 requestLayout();
350 }
351}