• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.widget;
18 
19 import com.android.internal.R;
20 
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.Canvas;
24 import android.graphics.drawable.Drawable;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.util.AttributeSet;
28 import android.view.Gravity;
29 import android.view.ViewDebug;
30 import android.view.accessibility.AccessibilityEvent;
31 
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  */
46 public 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 
CompoundButton(Context context)58     public CompoundButton(Context context) {
59         this(context, null);
60     }
61 
CompoundButton(Context context, AttributeSet attrs)62     public CompoundButton(Context context, AttributeSet attrs) {
63         this(context, attrs, 0);
64     }
65 
CompoundButton(Context context, AttributeSet attrs, int defStyle)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 
toggle()85     public void toggle() {
86         setChecked(!mChecked);
87     }
88 
89     @Override
performClick()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 
102     @ViewDebug.ExportedProperty
isChecked()103     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      */
setChecked(boolean checked)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             }
129 
130             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      */
setOnCheckedChangeListener(OnCheckedChangeListener listener)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      */
setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener)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          */
onCheckedChanged(CompoundButton buttonView, boolean isChecked)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      */
setButtonDrawable(int resid)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      */
setButtonDrawable(Drawable d)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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)211     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
onDraw(Canvas canvas)230     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
onCreateDrawableState(int extraSpace)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
drawableStateChanged()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
verifyDrawable(Drawable who)278     protected boolean verifyDrawable(Drawable who) {
279         return super.verifyDrawable(who) || who == mButtonDrawable;
280     }
281 
282     static class SavedState extends BaseSavedState {
283         boolean checked;
284 
285         /**
286          * Constructor called from {@link CompoundButton#onSaveInstanceState()}
287          */
SavedState(Parcelable superState)288         SavedState(Parcelable superState) {
289             super(superState);
290         }
291 
292         /**
293          * Constructor called from {@link #CREATOR}
294          */
SavedState(Parcel in)295         private SavedState(Parcel in) {
296             super(in);
297             checked = (Boolean)in.readValue(null);
298         }
299 
300         @Override
writeToParcel(Parcel out, int flags)301         public void writeToParcel(Parcel out, int flags) {
302             super.writeToParcel(out, flags);
303             out.writeValue(checked);
304         }
305 
306         @Override
toString()307         public String toString() {
308             return "CompoundButton.SavedState{"
309                     + Integer.toHexString(System.identityHashCode(this))
310                     + " checked=" + checked + "}";
311         }
312 
313         public static final Parcelable.Creator<SavedState> CREATOR
314                 = new Parcelable.Creator<SavedState>() {
315             public SavedState createFromParcel(Parcel in) {
316                 return new SavedState(in);
317             }
318 
319             public SavedState[] newArray(int size) {
320                 return new SavedState[size];
321             }
322         };
323     }
324 
325     @Override
onSaveInstanceState()326     public Parcelable onSaveInstanceState() {
327         // Force our ancestor class to save its state
328         setFreezesText(true);
329         Parcelable superState = super.onSaveInstanceState();
330 
331         SavedState ss = new SavedState(superState);
332 
333         ss.checked = isChecked();
334         return ss;
335     }
336 
337     @Override
onRestoreInstanceState(Parcelable state)338     public void onRestoreInstanceState(Parcelable state) {
339         SavedState ss = (SavedState) state;
340 
341         super.onRestoreInstanceState(ss.getSuperState());
342         setChecked(ss.checked);
343         requestLayout();
344     }
345 }
346