Home | History | Annotate | Download | only in widget
      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 
     59     public CompoundButton(Context context) {
     60         this(context, null);
     61     }
     62 
     63     public CompoundButton(Context context, AttributeSet attrs) {
     64         this(context, attrs, 0);
     65     }
     66 
     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 
     86     public void toggle() {
     87         setChecked(!mChecked);
     88     }
     89 
     90     @Override
     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
    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      */
    113     public void setChecked(boolean checked) {
    114         if (mChecked != checked) {
    115             mChecked = checked;
    116             refreshDrawableState();
    117             notifyViewAccessibilityStateChangedIfNeeded(
    118                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
    119 
    120             // Avoid infinite recursions if setChecked() is called from a listener
    121             if (mBroadcasting) {
    122                 return;
    123             }
    124 
    125             mBroadcasting = true;
    126             if (mOnCheckedChangeListener != null) {
    127                 mOnCheckedChangeListener.onCheckedChanged(this, mChecked);
    128             }
    129             if (mOnCheckedChangeWidgetListener != null) {
    130                 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
    131             }
    132 
    133             mBroadcasting = false;
    134         }
    135     }
    136 
    137     /**
    138      * Register a callback to be invoked when the checked state of this button
    139      * changes.
    140      *
    141      * @param listener the callback to call on checked state change
    142      */
    143     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    144         mOnCheckedChangeListener = listener;
    145     }
    146 
    147     /**
    148      * Register a callback to be invoked when the checked state of this button
    149      * changes. This callback is used for internal purpose only.
    150      *
    151      * @param listener the callback to call on checked state change
    152      * @hide
    153      */
    154     void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) {
    155         mOnCheckedChangeWidgetListener = listener;
    156     }
    157 
    158     /**
    159      * Interface definition for a callback to be invoked when the checked state
    160      * of a compound button changed.
    161      */
    162     public static interface OnCheckedChangeListener {
    163         /**
    164          * Called when the checked state of a compound button has changed.
    165          *
    166          * @param buttonView The compound button view whose state has changed.
    167          * @param isChecked  The new checked state of buttonView.
    168          */
    169         void onCheckedChanged(CompoundButton buttonView, boolean isChecked);
    170     }
    171 
    172     /**
    173      * Set the background to a given Drawable, identified by its resource id.
    174      *
    175      * @param resid the resource id of the drawable to use as the background
    176      */
    177     public void setButtonDrawable(int resid) {
    178         if (resid != 0 && resid == mButtonResource) {
    179             return;
    180         }
    181 
    182         mButtonResource = resid;
    183 
    184         Drawable d = null;
    185         if (mButtonResource != 0) {
    186             d = getResources().getDrawable(mButtonResource);
    187         }
    188         setButtonDrawable(d);
    189     }
    190 
    191     /**
    192      * Set the background to a given Drawable
    193      *
    194      * @param d The Drawable to use as the background
    195      */
    196     public void setButtonDrawable(Drawable d) {
    197         if (d != null) {
    198             if (mButtonDrawable != null) {
    199                 mButtonDrawable.setCallback(null);
    200                 unscheduleDrawable(mButtonDrawable);
    201             }
    202             d.setCallback(this);
    203             d.setVisible(getVisibility() == VISIBLE, false);
    204             mButtonDrawable = d;
    205             setMinHeight(mButtonDrawable.getIntrinsicHeight());
    206         }
    207 
    208         refreshDrawableState();
    209     }
    210 
    211     @Override
    212     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    213         super.onInitializeAccessibilityEvent(event);
    214         event.setClassName(CompoundButton.class.getName());
    215         event.setChecked(mChecked);
    216     }
    217 
    218     @Override
    219     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    220         super.onInitializeAccessibilityNodeInfo(info);
    221         info.setClassName(CompoundButton.class.getName());
    222         info.setCheckable(true);
    223         info.setChecked(mChecked);
    224     }
    225 
    226     @Override
    227     public int getCompoundPaddingLeft() {
    228         int padding = super.getCompoundPaddingLeft();
    229         if (!isLayoutRtl()) {
    230             final Drawable buttonDrawable = mButtonDrawable;
    231             if (buttonDrawable != null) {
    232                 padding += buttonDrawable.getIntrinsicWidth();
    233             }
    234         }
    235         return padding;
    236     }
    237 
    238     @Override
    239     public int getCompoundPaddingRight() {
    240         int padding = super.getCompoundPaddingRight();
    241         if (isLayoutRtl()) {
    242             final Drawable buttonDrawable = mButtonDrawable;
    243             if (buttonDrawable != null) {
    244                 padding += buttonDrawable.getIntrinsicWidth();
    245             }
    246         }
    247         return padding;
    248     }
    249 
    250     /**
    251      * @hide
    252      */
    253     @Override
    254     public int getHorizontalOffsetForDrawables() {
    255         final Drawable buttonDrawable = mButtonDrawable;
    256         return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0;
    257     }
    258 
    259     @Override
    260     protected void onDraw(Canvas canvas) {
    261         super.onDraw(canvas);
    262 
    263         final Drawable buttonDrawable = mButtonDrawable;
    264         if (buttonDrawable != null) {
    265             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
    266             final int drawableHeight = buttonDrawable.getIntrinsicHeight();
    267             final int drawableWidth = buttonDrawable.getIntrinsicWidth();
    268 
    269             int top = 0;
    270             switch (verticalGravity) {
    271                 case Gravity.BOTTOM:
    272                     top = getHeight() - drawableHeight;
    273                     break;
    274                 case Gravity.CENTER_VERTICAL:
    275                     top = (getHeight() - drawableHeight) / 2;
    276                     break;
    277             }
    278             int bottom = top + drawableHeight;
    279             int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
    280             int right = isLayoutRtl() ? getWidth() : drawableWidth;
    281 
    282             buttonDrawable.setBounds(left, top, right, bottom);
    283             buttonDrawable.draw(canvas);
    284         }
    285     }
    286 
    287     @Override
    288     protected int[] onCreateDrawableState(int extraSpace) {
    289         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    290         if (isChecked()) {
    291             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    292         }
    293         return drawableState;
    294     }
    295 
    296     @Override
    297     protected void drawableStateChanged() {
    298         super.drawableStateChanged();
    299 
    300         if (mButtonDrawable != null) {
    301             int[] myDrawableState = getDrawableState();
    302 
    303             // Set the state of the Drawable
    304             mButtonDrawable.setState(myDrawableState);
    305 
    306             invalidate();
    307         }
    308     }
    309 
    310     @Override
    311     protected boolean verifyDrawable(Drawable who) {
    312         return super.verifyDrawable(who) || who == mButtonDrawable;
    313     }
    314 
    315     @Override
    316     public void jumpDrawablesToCurrentState() {
    317         super.jumpDrawablesToCurrentState();
    318         if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState();
    319     }
    320 
    321     static class SavedState extends BaseSavedState {
    322         boolean checked;
    323 
    324         /**
    325          * Constructor called from {@link CompoundButton#onSaveInstanceState()}
    326          */
    327         SavedState(Parcelable superState) {
    328             super(superState);
    329         }
    330 
    331         /**
    332          * Constructor called from {@link #CREATOR}
    333          */
    334         private SavedState(Parcel in) {
    335             super(in);
    336             checked = (Boolean)in.readValue(null);
    337         }
    338 
    339         @Override
    340         public void writeToParcel(Parcel out, int flags) {
    341             super.writeToParcel(out, flags);
    342             out.writeValue(checked);
    343         }
    344 
    345         @Override
    346         public String toString() {
    347             return "CompoundButton.SavedState{"
    348                     + Integer.toHexString(System.identityHashCode(this))
    349                     + " checked=" + checked + "}";
    350         }
    351 
    352         public static final Parcelable.Creator<SavedState> CREATOR
    353                 = new Parcelable.Creator<SavedState>() {
    354             public SavedState createFromParcel(Parcel in) {
    355                 return new SavedState(in);
    356             }
    357 
    358             public SavedState[] newArray(int size) {
    359                 return new SavedState[size];
    360             }
    361         };
    362     }
    363 
    364     @Override
    365     public Parcelable onSaveInstanceState() {
    366         // Force our ancestor class to save its state
    367         setFreezesText(true);
    368         Parcelable superState = super.onSaveInstanceState();
    369 
    370         SavedState ss = new SavedState(superState);
    371 
    372         ss.checked = isChecked();
    373         return ss;
    374     }
    375 
    376     @Override
    377     public void onRestoreInstanceState(Parcelable state) {
    378         SavedState ss = (SavedState) state;
    379 
    380         super.onRestoreInstanceState(ss.getSuperState());
    381         setChecked(ss.checked);
    382         requestLayout();
    383     }
    384 }
    385