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