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