• 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 android.annotation.Nullable;
20 import android.graphics.PorterDuff;
21 import com.android.internal.R;
22 
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.TypedArray;
26 import android.graphics.Canvas;
27 import android.graphics.drawable.Drawable;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.util.AttributeSet;
31 import android.view.Gravity;
32 import android.view.ViewDebug;
33 import android.view.accessibility.AccessibilityEvent;
34 import android.view.accessibility.AccessibilityNodeInfo;
35 
36 /**
37  * <p>
38  * A button with two states, checked and unchecked. When the button is pressed
39  * or clicked, the state changes automatically.
40  * </p>
41  *
42  * <p><strong>XML attributes</strong></p>
43  * <p>
44  * See {@link android.R.styleable#CompoundButton
45  * CompoundButton Attributes}, {@link android.R.styleable#Button Button
46  * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link
47  * android.R.styleable#View View Attributes}
48  * </p>
49  */
50 public abstract class CompoundButton extends Button implements Checkable {
51     private boolean mChecked;
52     private int mButtonResource;
53     private boolean mBroadcasting;
54 
55     private Drawable mButtonDrawable;
56     private ColorStateList mButtonTintList = null;
57     private PorterDuff.Mode mButtonTintMode = null;
58     private boolean mHasButtonTint = false;
59     private boolean mHasButtonTintMode = false;
60 
61     private OnCheckedChangeListener mOnCheckedChangeListener;
62     private OnCheckedChangeListener mOnCheckedChangeWidgetListener;
63 
64     private static final int[] CHECKED_STATE_SET = {
65         R.attr.state_checked
66     };
67 
CompoundButton(Context context)68     public CompoundButton(Context context) {
69         this(context, null);
70     }
71 
CompoundButton(Context context, AttributeSet attrs)72     public CompoundButton(Context context, AttributeSet attrs) {
73         this(context, attrs, 0);
74     }
75 
CompoundButton(Context context, AttributeSet attrs, int defStyleAttr)76     public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) {
77         this(context, attrs, defStyleAttr, 0);
78     }
79 
CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)80     public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
81         super(context, attrs, defStyleAttr, defStyleRes);
82 
83         final TypedArray a = context.obtainStyledAttributes(
84                 attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes);
85 
86         final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button);
87         if (d != null) {
88             setButtonDrawable(d);
89         }
90 
91         if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) {
92             mButtonTintMode = Drawable.parseTintMode(a.getInt(
93                     R.styleable.CompoundButton_buttonTintMode, -1), mButtonTintMode);
94             mHasButtonTintMode = true;
95         }
96 
97         if (a.hasValue(R.styleable.CompoundButton_buttonTint)) {
98             mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint);
99             mHasButtonTint = true;
100         }
101 
102         final boolean checked = a.getBoolean(
103                 com.android.internal.R.styleable.CompoundButton_checked, false);
104         setChecked(checked);
105 
106         a.recycle();
107 
108         applyButtonTint();
109     }
110 
toggle()111     public void toggle() {
112         setChecked(!mChecked);
113     }
114 
115     @Override
performClick()116     public boolean performClick() {
117         /*
118          * XXX: These are tiny, need some surrounding 'expanded touch area',
119          * which will need to be implemented in Button if we only override
120          * performClick()
121          */
122 
123         /* When clicked, toggle the state */
124         toggle();
125         return super.performClick();
126     }
127 
128     @ViewDebug.ExportedProperty
isChecked()129     public boolean isChecked() {
130         return mChecked;
131     }
132 
133     /**
134      * <p>Changes the checked state of this button.</p>
135      *
136      * @param checked true to check the button, false to uncheck it
137      */
setChecked(boolean checked)138     public void setChecked(boolean checked) {
139         if (mChecked != checked) {
140             mChecked = checked;
141             refreshDrawableState();
142             notifyViewAccessibilityStateChangedIfNeeded(
143                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
144 
145             // Avoid infinite recursions if setChecked() is called from a listener
146             if (mBroadcasting) {
147                 return;
148             }
149 
150             mBroadcasting = true;
151             if (mOnCheckedChangeListener != null) {
152                 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
153             }
154             if (mOnCheckedChangeWidgetListener != null) {
155                 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
156             }
157 
158             mBroadcasting = false;
159         }
160     }
161 
162     /**
163      * Register a callback to be invoked when the checked state of this button
164      * changes.
165      *
166      * @param listener the callback to call on checked state change
167      */
setOnCheckedChangeListener(OnCheckedChangeListener listener)168     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
169         mOnCheckedChangeListener = listener;
170     }
171 
172     /**
173      * Register a callback to be invoked when the checked state of this button
174      * changes. This callback is used for internal purpose only.
175      *
176      * @param listener the callback to call on checked state change
177      * @hide
178      */
setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener)179     void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
180         mOnCheckedChangeWidgetListener = listener;
181     }
182 
183     /**
184      * Interface definition for a callback to be invoked when the checked state
185      * of a compound button changed.
186      */
187     public static interface OnCheckedChangeListener {
188         /**
189          * Called when the checked state of a compound button has changed.
190          *
191          * @param buttonView The compound button view whose state has changed.
192          * @param isChecked  The new checked state of buttonView.
193          */
onCheckedChanged(CompoundButton buttonView, boolean isChecked)194         void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
195     }
196 
197     /**
198      * Set the button graphic to a given Drawable, identified by its resource
199      * id.
200      *
201      * @param resid the resource id of the drawable to use as the button
202      *        graphic
203      */
setButtonDrawable(int resid)204     public void setButtonDrawable(int resid) {
205         if (resid != 0 && resid == mButtonResource) {
206             return;
207         }
208 
209         mButtonResource = resid;
210 
211         Drawable d = null;
212         if (mButtonResource != 0) {
213             d = getContext().getDrawable(mButtonResource);
214         }
215         setButtonDrawable(d);
216     }
217 
218     /**
219      * Set the button graphic to a given Drawable
220      *
221      * @param d The Drawable to use as the button graphic
222      */
setButtonDrawable(Drawable d)223     public void setButtonDrawable(Drawable d) {
224         if (mButtonDrawable != d) {
225             if (mButtonDrawable != null) {
226                 mButtonDrawable.setCallback(null);
227                 unscheduleDrawable(mButtonDrawable);
228             }
229 
230             mButtonDrawable = d;
231 
232             if (d != null) {
233                 d.setCallback(this);
234                 d.setLayoutDirection(getLayoutDirection());
235                 if (d.isStateful()) {
236                     d.setState(getDrawableState());
237                 }
238                 d.setVisible(getVisibility() == VISIBLE, false);
239                 setMinHeight(d.getIntrinsicHeight());
240                 applyButtonTint();
241             }
242         }
243     }
244 
245     /**
246      * Applies a tint to the button drawable. Does not modify the current tint
247      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
248      * <p>
249      * Subsequent calls to {@link #setButtonDrawable(Drawable)} will
250      * automatically mutate the drawable and apply the specified tint and tint
251      * mode using
252      * {@link Drawable#setTintList(ColorStateList)}.
253      *
254      * @param tint the tint to apply, may be {@code null} to clear tint
255      *
256      * @attr ref android.R.styleable#CompoundButton_buttonTint
257      * @see #setButtonTintList(ColorStateList)
258      * @see Drawable#setTintList(ColorStateList)
259      */
setButtonTintList(@ullable ColorStateList tint)260     public void setButtonTintList(@Nullable ColorStateList tint) {
261         mButtonTintList = tint;
262         mHasButtonTint = true;
263 
264         applyButtonTint();
265     }
266 
267     /**
268      * @return the tint applied to the button drawable
269      * @attr ref android.R.styleable#CompoundButton_buttonTint
270      * @see #setButtonTintList(ColorStateList)
271      */
272     @Nullable
getButtonTintList()273     public ColorStateList getButtonTintList() {
274         return mButtonTintList;
275     }
276 
277     /**
278      * Specifies the blending mode used to apply the tint specified by
279      * {@link #setButtonTintList(ColorStateList)}} to the button drawable. The
280      * default mode is {@link PorterDuff.Mode#SRC_IN}.
281      *
282      * @param tintMode the blending mode used to apply the tint, may be
283      *                 {@code null} to clear tint
284      * @attr ref android.R.styleable#CompoundButton_buttonTintMode
285      * @see #getButtonTintMode()
286      * @see Drawable#setTintMode(PorterDuff.Mode)
287      */
setButtonTintMode(@ullable PorterDuff.Mode tintMode)288     public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) {
289         mButtonTintMode = tintMode;
290         mHasButtonTintMode = true;
291 
292         applyButtonTint();
293     }
294 
295     /**
296      * @return the blending mode used to apply the tint to the button drawable
297      * @attr ref android.R.styleable#CompoundButton_buttonTintMode
298      * @see #setButtonTintMode(PorterDuff.Mode)
299      */
300     @Nullable
getButtonTintMode()301     public PorterDuff.Mode getButtonTintMode() {
302         return mButtonTintMode;
303     }
304 
applyButtonTint()305     private void applyButtonTint() {
306         if (mButtonDrawable != null && (mHasButtonTint || mHasButtonTintMode)) {
307             mButtonDrawable = mButtonDrawable.mutate();
308 
309             if (mHasButtonTint) {
310                 mButtonDrawable.setTintList(mButtonTintList);
311             }
312 
313             if (mHasButtonTintMode) {
314                 mButtonDrawable.setTintMode(mButtonTintMode);
315             }
316         }
317     }
318 
319     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)320     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
321         super.onInitializeAccessibilityEvent(event);
322         event.setClassName(CompoundButton.class.getName());
323         event.setChecked(mChecked);
324     }
325 
326     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)327     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
328         super.onInitializeAccessibilityNodeInfo(info);
329         info.setClassName(CompoundButton.class.getName());
330         info.setCheckable(true);
331         info.setChecked(mChecked);
332     }
333 
334     @Override
getCompoundPaddingLeft()335     public int getCompoundPaddingLeft() {
336         int padding = super.getCompoundPaddingLeft();
337         if (!isLayoutRtl()) {
338             final Drawable buttonDrawable = mButtonDrawable;
339             if (buttonDrawable != null) {
340                 padding += buttonDrawable.getIntrinsicWidth();
341             }
342         }
343         return padding;
344     }
345 
346     @Override
getCompoundPaddingRight()347     public int getCompoundPaddingRight() {
348         int padding = super.getCompoundPaddingRight();
349         if (isLayoutRtl()) {
350             final Drawable buttonDrawable = mButtonDrawable;
351             if (buttonDrawable != null) {
352                 padding += buttonDrawable.getIntrinsicWidth();
353             }
354         }
355         return padding;
356     }
357 
358     /**
359      * @hide
360      */
361     @Override
getHorizontalOffsetForDrawables()362     public int getHorizontalOffsetForDrawables() {
363         final Drawable buttonDrawable = mButtonDrawable;
364         return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
365     }
366 
367     @Override
onDraw(Canvas canvas)368     protected void onDraw(Canvas canvas) {
369         final Drawable buttonDrawable = mButtonDrawable;
370         if (buttonDrawable != null) {
371             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
372             final int drawableHeight = buttonDrawable.getIntrinsicHeight();
373             final int drawableWidth = buttonDrawable.getIntrinsicWidth();
374 
375             final int top;
376             switch (verticalGravity) {
377                 case Gravity.BOTTOM:
378                     top = getHeight() - drawableHeight;
379                     break;
380                 case Gravity.CENTER_VERTICAL:
381                     top = (getHeight() - drawableHeight) / 2;
382                     break;
383                 default:
384                     top = 0;
385             }
386             final int bottom = top + drawableHeight;
387             final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
388             final int right = isLayoutRtl() ? getWidth() : drawableWidth;
389 
390             buttonDrawable.setBounds(left, top, right, bottom);
391 
392             final Drawable background = getBackground();
393             if (background != null) {
394                 background.setHotspotBounds(left, top, right, bottom);
395             }
396         }
397 
398         super.onDraw(canvas);
399 
400         if (buttonDrawable != null) {
401             final int scrollX = mScrollX;
402             final int scrollY = mScrollY;
403             if (scrollX == 0 && scrollY == 0) {
404                 buttonDrawable.draw(canvas);
405             } else {
406                 canvas.translate(scrollX, scrollY);
407                 buttonDrawable.draw(canvas);
408                 canvas.translate(-scrollX, -scrollY);
409             }
410         }
411     }
412 
413     @Override
onCreateDrawableState(int extraSpace)414     protected int[] onCreateDrawableState(int extraSpace) {
415         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
416         if (isChecked()) {
417             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
418         }
419         return drawableState;
420     }
421 
422     @Override
drawableStateChanged()423     protected void drawableStateChanged() {
424         super.drawableStateChanged();
425 
426         if (mButtonDrawable != null) {
427             int[] myDrawableState = getDrawableState();
428 
429             // Set the state of the Drawable
430             mButtonDrawable.setState(myDrawableState);
431 
432             invalidate();
433         }
434     }
435 
436     @Override
drawableHotspotChanged(float x, float y)437     public void drawableHotspotChanged(float x, float y) {
438         super.drawableHotspotChanged(x, y);
439 
440         if (mButtonDrawable != null) {
441             mButtonDrawable.setHotspot(x, y);
442         }
443     }
444 
445     @Override
verifyDrawable(Drawable who)446     protected boolean verifyDrawable(Drawable who) {
447         return super.verifyDrawable(who) || who == mButtonDrawable;
448     }
449 
450     @Override
jumpDrawablesToCurrentState()451     public void jumpDrawablesToCurrentState() {
452         super.jumpDrawablesToCurrentState();
453         if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
454     }
455 
456     static class SavedState extends BaseSavedState {
457         boolean checked;
458 
459         /**
460          * Constructor called from {@link CompoundButton#onSaveInstanceState()}
461          */
SavedState(Parcelable superState)462         SavedState(Parcelable superState) {
463             super(superState);
464         }
465 
466         /**
467          * Constructor called from {@link #CREATOR}
468          */
SavedState(Parcel in)469         private SavedState(Parcel in) {
470             super(in);
471             checked = (Boolean)in.readValue(null);
472         }
473 
474         @Override
writeToParcel(Parcel out, int flags)475         public void writeToParcel(Parcel out, int flags) {
476             super.writeToParcel(out, flags);
477             out.writeValue(checked);
478         }
479 
480         @Override
toString()481         public String toString() {
482             return "CompoundButton.SavedState{"
483                     + Integer.toHexString(System.identityHashCode(this))
484                     + " checked=" + checked + "}";
485         }
486 
487         public static final Parcelable.Creator<SavedState> CREATOR
488                 = new Parcelable.Creator<SavedState>() {
489             public SavedState createFromParcel(Parcel in) {
490                 return new SavedState(in);
491             }
492 
493             public SavedState[] newArray(int size) {
494                 return new SavedState[size];
495             }
496         };
497     }
498 
499     @Override
onSaveInstanceState()500     public Parcelable onSaveInstanceState() {
501         Parcelable superState = super.onSaveInstanceState();
502 
503         SavedState ss = new SavedState(superState);
504 
505         ss.checked = isChecked();
506         return ss;
507     }
508 
509     @Override
onRestoreInstanceState(Parcelable state)510     public void onRestoreInstanceState(Parcelable state) {
511         SavedState ss = (SavedState) state;
512 
513         super.onRestoreInstanceState(ss.getSuperState());
514         setChecked(ss.checked);
515         requestLayout();
516     }
517 }
518