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.util.AttributeSet;
     26 import android.view.Gravity;
     27 import android.view.ViewDebug;
     28 import android.view.accessibility.AccessibilityEvent;
     29 import android.view.accessibility.AccessibilityNodeInfo;
     30 
     31 
     32 /**
     33  * An extension to TextView that supports the {@link android.widget.Checkable} interface.
     34  * This is useful when used in a {@link android.widget.ListView ListView} where the it's
     35  * {@link android.widget.ListView#setChoiceMode(int) setChoiceMode} has been set to
     36  * something other than {@link android.widget.ListView#CHOICE_MODE_NONE CHOICE_MODE_NONE}.
     37  *
     38  * @attr ref android.R.styleable#CheckedTextView_checked
     39  * @attr ref android.R.styleable#CheckedTextView_checkMark
     40  */
     41 public class CheckedTextView extends TextView implements Checkable {
     42     private boolean mChecked;
     43     private int mCheckMarkResource;
     44     private Drawable mCheckMarkDrawable;
     45     private int mBasePadding;
     46     private int mCheckMarkWidth;
     47     private boolean mNeedRequestlayout;
     48 
     49     private static final int[] CHECKED_STATE_SET = {
     50         R.attr.state_checked
     51     };
     52 
     53     public CheckedTextView(Context context) {
     54         this(context, null);
     55     }
     56 
     57     public CheckedTextView(Context context, AttributeSet attrs) {
     58         this(context, attrs, R.attr.checkedTextViewStyle);
     59     }
     60 
     61     public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
     62         super(context, attrs, defStyle);
     63 
     64         TypedArray a = context.obtainStyledAttributes(attrs,
     65                 R.styleable.CheckedTextView, defStyle, 0);
     66 
     67         Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark);
     68         if (d != null) {
     69             setCheckMarkDrawable(d);
     70         }
     71 
     72         boolean checked = a.getBoolean(R.styleable.CheckedTextView_checked, false);
     73         setChecked(checked);
     74 
     75         a.recycle();
     76     }
     77 
     78     public void toggle() {
     79         setChecked(!mChecked);
     80     }
     81 
     82     @ViewDebug.ExportedProperty
     83     public boolean isChecked() {
     84         return mChecked;
     85     }
     86 
     87     /**
     88      * <p>Changes the checked state of this text view.</p>
     89      *
     90      * @param checked true to check the text, false to uncheck it
     91      */
     92     public void setChecked(boolean checked) {
     93         if (mChecked != checked) {
     94             mChecked = checked;
     95             refreshDrawableState();
     96             notifyViewAccessibilityStateChangedIfNeeded(
     97                     AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
     98         }
     99     }
    100 
    101 
    102     /**
    103      * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
    104      * when {@link #isChecked()} is true.
    105      *
    106      * @param resid The Drawable to use for the checkmark.
    107      *
    108      * @see #setCheckMarkDrawable(Drawable)
    109      * @see #getCheckMarkDrawable()
    110      *
    111      * @attr ref android.R.styleable#CheckedTextView_checkMark
    112      */
    113     public void setCheckMarkDrawable(int resid) {
    114         if (resid != 0 && resid == mCheckMarkResource) {
    115             return;
    116         }
    117 
    118         mCheckMarkResource = resid;
    119 
    120         Drawable d = null;
    121         if (mCheckMarkResource != 0) {
    122             d = getResources().getDrawable(mCheckMarkResource);
    123         }
    124         setCheckMarkDrawable(d);
    125     }
    126 
    127     /**
    128      * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true.
    129      *
    130      * @param d The Drawable to use for the checkmark.
    131      *
    132      * @see #setCheckMarkDrawable(int)
    133      * @see #getCheckMarkDrawable()
    134      *
    135      * @attr ref android.R.styleable#CheckedTextView_checkMark
    136      */
    137     public void setCheckMarkDrawable(Drawable d) {
    138         if (mCheckMarkDrawable != null) {
    139             mCheckMarkDrawable.setCallback(null);
    140             unscheduleDrawable(mCheckMarkDrawable);
    141         }
    142         mNeedRequestlayout = (d != mCheckMarkDrawable);
    143         if (d != null) {
    144             d.setCallback(this);
    145             d.setVisible(getVisibility() == VISIBLE, false);
    146             d.setState(CHECKED_STATE_SET);
    147             setMinHeight(d.getIntrinsicHeight());
    148 
    149             mCheckMarkWidth = d.getIntrinsicWidth();
    150             d.setState(getDrawableState());
    151         } else {
    152             mCheckMarkWidth = 0;
    153         }
    154         mCheckMarkDrawable = d;
    155         // Do padding resolution. This will call internalSetPadding() and do a requestLayout() if needed.
    156         resolvePadding();
    157     }
    158 
    159     /**
    160      * Gets the checkmark drawable
    161      *
    162      * @return The drawable use to represent the checkmark, if any.
    163      *
    164      * @see #setCheckMarkDrawable(Drawable)
    165      * @see #setCheckMarkDrawable(int)
    166      *
    167      * @attr ref android.R.styleable#CheckedTextView_checkMark
    168      */
    169     public Drawable getCheckMarkDrawable() {
    170         return mCheckMarkDrawable;
    171     }
    172 
    173     /**
    174      * @hide
    175      */
    176     @Override
    177     protected void internalSetPadding(int left, int top, int right, int bottom) {
    178         super.internalSetPadding(left, top, right, bottom);
    179         setBasePadding(isLayoutRtl());
    180     }
    181 
    182     @Override
    183     public void onRtlPropertiesChanged(int layoutDirection) {
    184         super.onRtlPropertiesChanged(layoutDirection);
    185         updatePadding();
    186     }
    187 
    188     private void updatePadding() {
    189         resetPaddingToInitialValues();
    190         int newPadding = (mCheckMarkDrawable != null) ?
    191                 mCheckMarkWidth + mBasePadding : mBasePadding;
    192         if (isLayoutRtl()) {
    193             mNeedRequestlayout |= (mPaddingLeft != newPadding);
    194             mPaddingLeft = newPadding;
    195         } else {
    196             mNeedRequestlayout |= (mPaddingRight != newPadding);
    197             mPaddingRight = newPadding;
    198         }
    199         if (mNeedRequestlayout) {
    200             requestLayout();
    201             mNeedRequestlayout = false;
    202         }
    203     }
    204 
    205     private void setBasePadding(boolean isLayoutRtl) {
    206         if (isLayoutRtl) {
    207             mBasePadding = mPaddingLeft;
    208         } else {
    209             mBasePadding = mPaddingRight;
    210         }
    211     }
    212 
    213     @Override
    214     protected void onDraw(Canvas canvas) {
    215         super.onDraw(canvas);
    216 
    217         final Drawable checkMarkDrawable = mCheckMarkDrawable;
    218         if (checkMarkDrawable != null) {
    219             final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
    220             final int height = checkMarkDrawable.getIntrinsicHeight();
    221 
    222             int y = 0;
    223 
    224             switch (verticalGravity) {
    225                 case Gravity.BOTTOM:
    226                     y = getHeight() - height;
    227                     break;
    228                 case Gravity.CENTER_VERTICAL:
    229                     y = (getHeight() - height) / 2;
    230                     break;
    231             }
    232 
    233             final boolean isLayoutRtl = isLayoutRtl();
    234             final int width = getWidth();
    235             final int top = y;
    236             final int bottom = top + height;
    237             final int left;
    238             final int right;
    239             if (isLayoutRtl) {
    240                 left = mBasePadding;
    241                 right = left + mCheckMarkWidth;
    242             } else {
    243                 right = width - mBasePadding;
    244                 left = right - mCheckMarkWidth;
    245             }
    246             checkMarkDrawable.setBounds(mScrollX + left, top, mScrollX + right, bottom);
    247             checkMarkDrawable.draw(canvas);
    248         }
    249     }
    250 
    251     @Override
    252     protected int[] onCreateDrawableState(int extraSpace) {
    253         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    254         if (isChecked()) {
    255             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    256         }
    257         return drawableState;
    258     }
    259 
    260     @Override
    261     protected void drawableStateChanged() {
    262         super.drawableStateChanged();
    263 
    264         if (mCheckMarkDrawable != null) {
    265             int[] myDrawableState = getDrawableState();
    266 
    267             // Set the state of the Drawable
    268             mCheckMarkDrawable.setState(myDrawableState);
    269 
    270             invalidate();
    271         }
    272     }
    273 
    274     @Override
    275     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    276         super.onInitializeAccessibilityEvent(event);
    277         event.setClassName(CheckedTextView.class.getName());
    278         event.setChecked(mChecked);
    279     }
    280 
    281     @Override
    282     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    283         super.onInitializeAccessibilityNodeInfo(info);
    284         info.setClassName(CheckedTextView.class.getName());
    285         info.setCheckable(true);
    286         info.setChecked(mChecked);
    287     }
    288 }
    289