• 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.accessibility.AccessibilityEvent;
30 
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  */
45 public 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 
CompoundButton(Context context)57     public CompoundButton(Context context) {
58         this(context, null);
59     }
60 
CompoundButton(Context context, AttributeSet attrs)61     public CompoundButton(Context context, AttributeSet attrs) {
62         this(context, attrs, 0);
63     }
64 
CompoundButton(Context context, AttributeSet attrs, int defStyle)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 
toggle()84     public void toggle() {
85         setChecked(!mChecked);
86     }
87 
88     @Override
performClick()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 
isChecked()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      */
setChecked(boolean checked)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             }
127 
128             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      */
setOnCheckedChangeListener(OnCheckedChangeListener listener)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      */
setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener)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          */
onCheckedChanged(CompoundButton buttonView, boolean isChecked)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      */
setButtonDrawable(int resid)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      */
setButtonDrawable(Drawable d)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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)209     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
onDraw(Canvas canvas)228     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
onCreateDrawableState(int extraSpace)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
drawableStateChanged()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
verifyDrawable(Drawable who)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          */
SavedState(Parcelable superState)286         SavedState(Parcelable superState) {
287             super(superState);
288         }
289 
290         /**
291          * Constructor called from {@link #CREATOR}
292          */
SavedState(Parcel in)293         private SavedState(Parcel in) {
294             super(in);
295             checked = (Boolean)in.readValue(null);
296         }
297 
298         @Override
writeToParcel(Parcel out, int flags)299         public void writeToParcel(Parcel out, int flags) {
300             super.writeToParcel(out, flags);
301             out.writeValue(checked);
302         }
303 
304         @Override
toString()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
onSaveInstanceState()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
onRestoreInstanceState(Parcelable state)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 }
344