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