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