Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2010 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.content.Context;
     20 import android.content.res.ColorStateList;
     21 import android.content.res.Resources;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Canvas;
     24 import android.graphics.Paint;
     25 import android.graphics.Rect;
     26 import android.graphics.Typeface;
     27 import android.graphics.drawable.Drawable;
     28 import android.text.Layout;
     29 import android.text.StaticLayout;
     30 import android.text.TextPaint;
     31 import android.text.TextUtils;
     32 import android.text.method.AllCapsTransformationMethod;
     33 import android.text.method.TransformationMethod2;
     34 import android.util.AttributeSet;
     35 import android.view.Gravity;
     36 import android.view.MotionEvent;
     37 import android.view.VelocityTracker;
     38 import android.view.ViewConfiguration;
     39 import android.view.accessibility.AccessibilityEvent;
     40 import android.view.accessibility.AccessibilityNodeInfo;
     41 
     42 import com.android.internal.R;
     43 
     44 /**
     45  * A Switch is a two-state toggle switch widget that can select between two
     46  * options. The user may drag the "thumb" back and forth to choose the selected option,
     47  * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
     48  * property controls the text displayed in the label for the switch, whereas the
     49  * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
     50  * controls the text on the thumb. Similarly, the
     51  * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
     52  * setTypeface() methods control the typeface and style of label text, whereas the
     53  * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
     54  * the related seSwitchTypeface() methods control that of the thumb.
     55  *
     56  * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
     57  * guide.</p>
     58  *
     59  * @attr ref android.R.styleable#Switch_textOn
     60  * @attr ref android.R.styleable#Switch_textOff
     61  * @attr ref android.R.styleable#Switch_switchMinWidth
     62  * @attr ref android.R.styleable#Switch_switchPadding
     63  * @attr ref android.R.styleable#Switch_switchTextAppearance
     64  * @attr ref android.R.styleable#Switch_thumb
     65  * @attr ref android.R.styleable#Switch_thumbTextPadding
     66  * @attr ref android.R.styleable#Switch_track
     67  */
     68 public class Switch extends CompoundButton {
     69     private static final int TOUCH_MODE_IDLE = 0;
     70     private static final int TOUCH_MODE_DOWN = 1;
     71     private static final int TOUCH_MODE_DRAGGING = 2;
     72 
     73     // Enum for the "typeface" XML parameter.
     74     private static final int SANS = 1;
     75     private static final int SERIF = 2;
     76     private static final int MONOSPACE = 3;
     77 
     78     private Drawable mThumbDrawable;
     79     private Drawable mTrackDrawable;
     80     private int mThumbTextPadding;
     81     private int mSwitchMinWidth;
     82     private int mSwitchPadding;
     83     private CharSequence mTextOn;
     84     private CharSequence mTextOff;
     85 
     86     private int mTouchMode;
     87     private int mTouchSlop;
     88     private float mTouchX;
     89     private float mTouchY;
     90     private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     91     private int mMinFlingVelocity;
     92 
     93     private float mThumbPosition;
     94     private int mSwitchWidth;
     95     private int mSwitchHeight;
     96     private int mThumbWidth; // Does not include padding
     97 
     98     private int mSwitchLeft;
     99     private int mSwitchTop;
    100     private int mSwitchRight;
    101     private int mSwitchBottom;
    102 
    103     private TextPaint mTextPaint;
    104     private ColorStateList mTextColors;
    105     private Layout mOnLayout;
    106     private Layout mOffLayout;
    107     private TransformationMethod2 mSwitchTransformationMethod;
    108 
    109     @SuppressWarnings("hiding")
    110     private final Rect mTempRect = new Rect();
    111 
    112     private static final int[] CHECKED_STATE_SET = {
    113         R.attr.state_checked
    114     };
    115 
    116     /**
    117      * Construct a new Switch with default styling.
    118      *
    119      * @param context The Context that will determine this widget's theming.
    120      */
    121     public Switch(Context context) {
    122         this(context, null);
    123     }
    124 
    125     /**
    126      * Construct a new Switch with default styling, overriding specific style
    127      * attributes as requested.
    128      *
    129      * @param context The Context that will determine this widget's theming.
    130      * @param attrs Specification of attributes that should deviate from default styling.
    131      */
    132     public Switch(Context context, AttributeSet attrs) {
    133         this(context, attrs, com.android.internal.R.attr.switchStyle);
    134     }
    135 
    136     /**
    137      * Construct a new Switch with a default style determined by the given theme attribute,
    138      * overriding specific style attributes as requested.
    139      *
    140      * @param context The Context that will determine this widget's theming.
    141      * @param attrs Specification of attributes that should deviate from the default styling.
    142      * @param defStyle An attribute ID within the active theme containing a reference to the
    143      *                 default style for this widget. e.g. android.R.attr.switchStyle.
    144      */
    145     public Switch(Context context, AttributeSet attrs, int defStyle) {
    146         super(context, attrs, defStyle);
    147 
    148         mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    149         Resources res = getResources();
    150         mTextPaint.density = res.getDisplayMetrics().density;
    151         mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
    152 
    153         TypedArray a = context.obtainStyledAttributes(attrs,
    154                 com.android.internal.R.styleable.Switch, defStyle, 0);
    155 
    156         mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
    157         mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
    158         mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
    159         mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
    160         mThumbTextPadding = a.getDimensionPixelSize(
    161                 com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
    162         mSwitchMinWidth = a.getDimensionPixelSize(
    163                 com.android.internal.R.styleable.Switch_switchMinWidth, 0);
    164         mSwitchPadding = a.getDimensionPixelSize(
    165                 com.android.internal.R.styleable.Switch_switchPadding, 0);
    166 
    167         int appearance = a.getResourceId(
    168                 com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
    169         if (appearance != 0) {
    170             setSwitchTextAppearance(context, appearance);
    171         }
    172         a.recycle();
    173 
    174         ViewConfiguration config = ViewConfiguration.get(context);
    175         mTouchSlop = config.getScaledTouchSlop();
    176         mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
    177 
    178         // Refresh display with current params
    179         refreshDrawableState();
    180         setChecked(isChecked());
    181     }
    182 
    183     /**
    184      * Sets the switch text color, size, style, hint color, and highlight color
    185      * from the specified TextAppearance resource.
    186      *
    187      * @attr ref android.R.styleable#Switch_switchTextAppearance
    188      */
    189     public void setSwitchTextAppearance(Context context, int resid) {
    190         TypedArray appearance =
    191                 context.obtainStyledAttributes(resid,
    192                         com.android.internal.R.styleable.TextAppearance);
    193 
    194         ColorStateList colors;
    195         int ts;
    196 
    197         colors = appearance.getColorStateList(com.android.internal.R.styleable.
    198                 TextAppearance_textColor);
    199         if (colors != null) {
    200             mTextColors = colors;
    201         } else {
    202             // If no color set in TextAppearance, default to the view's textColor
    203             mTextColors = getTextColors();
    204         }
    205 
    206         ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
    207                 TextAppearance_textSize, 0);
    208         if (ts != 0) {
    209             if (ts != mTextPaint.getTextSize()) {
    210                 mTextPaint.setTextSize(ts);
    211                 requestLayout();
    212             }
    213         }
    214 
    215         int typefaceIndex, styleIndex;
    216 
    217         typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
    218                 TextAppearance_typeface, -1);
    219         styleIndex = appearance.getInt(com.android.internal.R.styleable.
    220                 TextAppearance_textStyle, -1);
    221 
    222         setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
    223 
    224         boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
    225                 TextAppearance_textAllCaps, false);
    226         if (allCaps) {
    227             mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
    228             mSwitchTransformationMethod.setLengthChangesAllowed(true);
    229         } else {
    230             mSwitchTransformationMethod = null;
    231         }
    232 
    233         appearance.recycle();
    234     }
    235 
    236     private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
    237         Typeface tf = null;
    238         switch (typefaceIndex) {
    239             case SANS:
    240                 tf = Typeface.SANS_SERIF;
    241                 break;
    242 
    243             case SERIF:
    244                 tf = Typeface.SERIF;
    245                 break;
    246 
    247             case MONOSPACE:
    248                 tf = Typeface.MONOSPACE;
    249                 break;
    250         }
    251 
    252         setSwitchTypeface(tf, styleIndex);
    253     }
    254 
    255     /**
    256      * Sets the typeface and style in which the text should be displayed on the
    257      * switch, and turns on the fake bold and italic bits in the Paint if the
    258      * Typeface that you provided does not have all the bits in the
    259      * style that you specified.
    260      */
    261     public void setSwitchTypeface(Typeface tf, int style) {
    262         if (style > 0) {
    263             if (tf == null) {
    264                 tf = Typeface.defaultFromStyle(style);
    265             } else {
    266                 tf = Typeface.create(tf, style);
    267             }
    268 
    269             setSwitchTypeface(tf);
    270             // now compute what (if any) algorithmic styling is needed
    271             int typefaceStyle = tf != null ? tf.getStyle() : 0;
    272             int need = style & ~typefaceStyle;
    273             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
    274             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    275         } else {
    276             mTextPaint.setFakeBoldText(false);
    277             mTextPaint.setTextSkewX(0);
    278             setSwitchTypeface(tf);
    279         }
    280     }
    281 
    282     /**
    283      * Sets the typeface in which the text should be displayed on the switch.
    284      * Note that not all Typeface families actually have bold and italic
    285      * variants, so you may need to use
    286      * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
    287      * that you actually want.
    288      *
    289      * @attr ref android.R.styleable#TextView_typeface
    290      * @attr ref android.R.styleable#TextView_textStyle
    291      */
    292     public void setSwitchTypeface(Typeface tf) {
    293         if (mTextPaint.getTypeface() != tf) {
    294             mTextPaint.setTypeface(tf);
    295 
    296             requestLayout();
    297             invalidate();
    298         }
    299     }
    300 
    301     /**
    302      * Set the amount of horizontal padding between the switch and the associated text.
    303      *
    304      * @param pixels Amount of padding in pixels
    305      *
    306      * @attr ref android.R.styleable#Switch_switchPadding
    307      */
    308     public void setSwitchPadding(int pixels) {
    309         mSwitchPadding = pixels;
    310         requestLayout();
    311     }
    312 
    313     /**
    314      * Get the amount of horizontal padding between the switch and the associated text.
    315      *
    316      * @return Amount of padding in pixels
    317      *
    318      * @attr ref android.R.styleable#Switch_switchPadding
    319      */
    320     public int getSwitchPadding() {
    321         return mSwitchPadding;
    322     }
    323 
    324     /**
    325      * Set the minimum width of the switch in pixels. The switch's width will be the maximum
    326      * of this value and its measured width as determined by the switch drawables and text used.
    327      *
    328      * @param pixels Minimum width of the switch in pixels
    329      *
    330      * @attr ref android.R.styleable#Switch_switchMinWidth
    331      */
    332     public void setSwitchMinWidth(int pixels) {
    333         mSwitchMinWidth = pixels;
    334         requestLayout();
    335     }
    336 
    337     /**
    338      * Get the minimum width of the switch in pixels. The switch's width will be the maximum
    339      * of this value and its measured width as determined by the switch drawables and text used.
    340      *
    341      * @return Minimum width of the switch in pixels
    342      *
    343      * @attr ref android.R.styleable#Switch_switchMinWidth
    344      */
    345     public int getSwitchMinWidth() {
    346         return mSwitchMinWidth;
    347     }
    348 
    349     /**
    350      * Set the horizontal padding around the text drawn on the switch itself.
    351      *
    352      * @param pixels Horizontal padding for switch thumb text in pixels
    353      *
    354      * @attr ref android.R.styleable#Switch_thumbTextPadding
    355      */
    356     public void setThumbTextPadding(int pixels) {
    357         mThumbTextPadding = pixels;
    358         requestLayout();
    359     }
    360 
    361     /**
    362      * Get the horizontal padding around the text drawn on the switch itself.
    363      *
    364      * @return Horizontal padding for switch thumb text in pixels
    365      *
    366      * @attr ref android.R.styleable#Switch_thumbTextPadding
    367      */
    368     public int getThumbTextPadding() {
    369         return mThumbTextPadding;
    370     }
    371 
    372     /**
    373      * Set the drawable used for the track that the switch slides within.
    374      *
    375      * @param track Track drawable
    376      *
    377      * @attr ref android.R.styleable#Switch_track
    378      */
    379     public void setTrackDrawable(Drawable track) {
    380         mTrackDrawable = track;
    381         requestLayout();
    382     }
    383 
    384     /**
    385      * Set the drawable used for the track that the switch slides within.
    386      *
    387      * @param resId Resource ID of a track drawable
    388      *
    389      * @attr ref android.R.styleable#Switch_track
    390      */
    391     public void setTrackResource(int resId) {
    392         setTrackDrawable(getContext().getResources().getDrawable(resId));
    393     }
    394 
    395     /**
    396      * Get the drawable used for the track that the switch slides within.
    397      *
    398      * @return Track drawable
    399      *
    400      * @attr ref android.R.styleable#Switch_track
    401      */
    402     public Drawable getTrackDrawable() {
    403         return mTrackDrawable;
    404     }
    405 
    406     /**
    407      * Set the drawable used for the switch "thumb" - the piece that the user
    408      * can physically touch and drag along the track.
    409      *
    410      * @param thumb Thumb drawable
    411      *
    412      * @attr ref android.R.styleable#Switch_thumb
    413      */
    414     public void setThumbDrawable(Drawable thumb) {
    415         mThumbDrawable = thumb;
    416         requestLayout();
    417     }
    418 
    419     /**
    420      * Set the drawable used for the switch "thumb" - the piece that the user
    421      * can physically touch and drag along the track.
    422      *
    423      * @param resId Resource ID of a thumb drawable
    424      *
    425      * @attr ref android.R.styleable#Switch_thumb
    426      */
    427     public void setThumbResource(int resId) {
    428         setThumbDrawable(getContext().getResources().getDrawable(resId));
    429     }
    430 
    431     /**
    432      * Get the drawable used for the switch "thumb" - the piece that the user
    433      * can physically touch and drag along the track.
    434      *
    435      * @return Thumb drawable
    436      *
    437      * @attr ref android.R.styleable#Switch_thumb
    438      */
    439     public Drawable getThumbDrawable() {
    440         return mThumbDrawable;
    441     }
    442 
    443     /**
    444      * Returns the text displayed when the button is in the checked state.
    445      *
    446      * @attr ref android.R.styleable#Switch_textOn
    447      */
    448     public CharSequence getTextOn() {
    449         return mTextOn;
    450     }
    451 
    452     /**
    453      * Sets the text displayed when the button is in the checked state.
    454      *
    455      * @attr ref android.R.styleable#Switch_textOn
    456      */
    457     public void setTextOn(CharSequence textOn) {
    458         mTextOn = textOn;
    459         requestLayout();
    460     }
    461 
    462     /**
    463      * Returns the text displayed when the button is not in the checked state.
    464      *
    465      * @attr ref android.R.styleable#Switch_textOff
    466      */
    467     public CharSequence getTextOff() {
    468         return mTextOff;
    469     }
    470 
    471     /**
    472      * Sets the text displayed when the button is not in the checked state.
    473      *
    474      * @attr ref android.R.styleable#Switch_textOff
    475      */
    476     public void setTextOff(CharSequence textOff) {
    477         mTextOff = textOff;
    478         requestLayout();
    479     }
    480 
    481     @Override
    482     public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    483         if (mOnLayout == null) {
    484             mOnLayout = makeLayout(mTextOn);
    485         }
    486         if (mOffLayout == null) {
    487             mOffLayout = makeLayout(mTextOff);
    488         }
    489 
    490         mTrackDrawable.getPadding(mTempRect);
    491         final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
    492         final int switchWidth = Math.max(mSwitchMinWidth,
    493                 maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
    494         final int switchHeight = mTrackDrawable.getIntrinsicHeight();
    495 
    496         mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
    497 
    498         mSwitchWidth = switchWidth;
    499         mSwitchHeight = switchHeight;
    500 
    501         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    502         final int measuredHeight = getMeasuredHeight();
    503         if (measuredHeight < switchHeight) {
    504             setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
    505         }
    506     }
    507 
    508     @Override
    509     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    510         super.onPopulateAccessibilityEvent(event);
    511         Layout layout =  isChecked() ? mOnLayout : mOffLayout;
    512         if (layout != null && !TextUtils.isEmpty(layout.getText())) {
    513             event.getText().add(layout.getText());
    514         }
    515     }
    516 
    517     private Layout makeLayout(CharSequence text) {
    518         final CharSequence transformed = (mSwitchTransformationMethod != null)
    519                     ? mSwitchTransformationMethod.getTransformation(text, this)
    520                     : text;
    521 
    522         return new StaticLayout(transformed, mTextPaint,
    523                 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)),
    524                 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
    525     }
    526 
    527     /**
    528      * @return true if (x, y) is within the target area of the switch thumb
    529      */
    530     private boolean hitThumb(float x, float y) {
    531         mThumbDrawable.getPadding(mTempRect);
    532         final int thumbTop = mSwitchTop - mTouchSlop;
    533         final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
    534         final int thumbRight = thumbLeft + mThumbWidth +
    535                 mTempRect.left + mTempRect.right + mTouchSlop;
    536         final int thumbBottom = mSwitchBottom + mTouchSlop;
    537         return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
    538     }
    539 
    540     @Override
    541     public boolean onTouchEvent(MotionEvent ev) {
    542         mVelocityTracker.addMovement(ev);
    543         final int action = ev.getActionMasked();
    544         switch (action) {
    545             case MotionEvent.ACTION_DOWN: {
    546                 final float x = ev.getX();
    547                 final float y = ev.getY();
    548                 if (isEnabled() && hitThumb(x, y)) {
    549                     mTouchMode = TOUCH_MODE_DOWN;
    550                     mTouchX = x;
    551                     mTouchY = y;
    552                 }
    553                 break;
    554             }
    555 
    556             case MotionEvent.ACTION_MOVE: {
    557                 switch (mTouchMode) {
    558                     case TOUCH_MODE_IDLE:
    559                         // Didn't target the thumb, treat normally.
    560                         break;
    561 
    562                     case TOUCH_MODE_DOWN: {
    563                         final float x = ev.getX();
    564                         final float y = ev.getY();
    565                         if (Math.abs(x - mTouchX) > mTouchSlop ||
    566                                 Math.abs(y - mTouchY) > mTouchSlop) {
    567                             mTouchMode = TOUCH_MODE_DRAGGING;
    568                             getParent().requestDisallowInterceptTouchEvent(true);
    569                             mTouchX = x;
    570                             mTouchY = y;
    571                             return true;
    572                         }
    573                         break;
    574                     }
    575 
    576                     case TOUCH_MODE_DRAGGING: {
    577                         final float x = ev.getX();
    578                         final float dx = x - mTouchX;
    579                         float newPos = Math.max(0,
    580                                 Math.min(mThumbPosition + dx, getThumbScrollRange()));
    581                         if (newPos != mThumbPosition) {
    582                             mThumbPosition = newPos;
    583                             mTouchX = x;
    584                             invalidate();
    585                         }
    586                         return true;
    587                     }
    588                 }
    589                 break;
    590             }
    591 
    592             case MotionEvent.ACTION_UP:
    593             case MotionEvent.ACTION_CANCEL: {
    594                 if (mTouchMode == TOUCH_MODE_DRAGGING) {
    595                     stopDrag(ev);
    596                     return true;
    597                 }
    598                 mTouchMode = TOUCH_MODE_IDLE;
    599                 mVelocityTracker.clear();
    600                 break;
    601             }
    602         }
    603 
    604         return super.onTouchEvent(ev);
    605     }
    606 
    607     private void cancelSuperTouch(MotionEvent ev) {
    608         MotionEvent cancel = MotionEvent.obtain(ev);
    609         cancel.setAction(MotionEvent.ACTION_CANCEL);
    610         super.onTouchEvent(cancel);
    611         cancel.recycle();
    612     }
    613 
    614     /**
    615      * Called from onTouchEvent to end a drag operation.
    616      *
    617      * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
    618      */
    619     private void stopDrag(MotionEvent ev) {
    620         mTouchMode = TOUCH_MODE_IDLE;
    621         // Up and not canceled, also checks the switch has not been disabled during the drag
    622         boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
    623 
    624         cancelSuperTouch(ev);
    625 
    626         if (commitChange) {
    627             boolean newState;
    628             mVelocityTracker.computeCurrentVelocity(1000);
    629             float xvel = mVelocityTracker.getXVelocity();
    630             if (Math.abs(xvel) > mMinFlingVelocity) {
    631                 newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
    632             } else {
    633                 newState = getTargetCheckedState();
    634             }
    635             animateThumbToCheckedState(newState);
    636         } else {
    637             animateThumbToCheckedState(isChecked());
    638         }
    639     }
    640 
    641     private void animateThumbToCheckedState(boolean newCheckedState) {
    642         // TODO animate!
    643         //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
    644         //mThumbPosition = targetPos;
    645         setChecked(newCheckedState);
    646     }
    647 
    648     private boolean getTargetCheckedState() {
    649         if (isLayoutRtl()) {
    650             return mThumbPosition <= getThumbScrollRange() / 2;
    651         } else {
    652             return mThumbPosition >= getThumbScrollRange() / 2;
    653         }
    654     }
    655 
    656     private void setThumbPosition(boolean checked) {
    657         if (isLayoutRtl()) {
    658             mThumbPosition = checked ? 0 : getThumbScrollRange();
    659         } else {
    660             mThumbPosition = checked ? getThumbScrollRange() : 0;
    661         }
    662     }
    663 
    664     @Override
    665     public void setChecked(boolean checked) {
    666         super.setChecked(checked);
    667         setThumbPosition(isChecked());
    668         invalidate();
    669     }
    670 
    671     @Override
    672     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    673         super.onLayout(changed, left, top, right, bottom);
    674 
    675         setThumbPosition(isChecked());
    676 
    677         int switchRight;
    678         int switchLeft;
    679 
    680         if (isLayoutRtl()) {
    681             switchLeft = getPaddingLeft();
    682             switchRight = switchLeft + mSwitchWidth;
    683         } else {
    684             switchRight = getWidth() - getPaddingRight();
    685             switchLeft = switchRight - mSwitchWidth;
    686         }
    687 
    688         int switchTop = 0;
    689         int switchBottom = 0;
    690         switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
    691             default:
    692             case Gravity.TOP:
    693                 switchTop = getPaddingTop();
    694                 switchBottom = switchTop + mSwitchHeight;
    695                 break;
    696 
    697             case Gravity.CENTER_VERTICAL:
    698                 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
    699                         mSwitchHeight / 2;
    700                 switchBottom = switchTop + mSwitchHeight;
    701                 break;
    702 
    703             case Gravity.BOTTOM:
    704                 switchBottom = getHeight() - getPaddingBottom();
    705                 switchTop = switchBottom - mSwitchHeight;
    706                 break;
    707         }
    708 
    709         mSwitchLeft = switchLeft;
    710         mSwitchTop = switchTop;
    711         mSwitchBottom = switchBottom;
    712         mSwitchRight = switchRight;
    713     }
    714 
    715     @Override
    716     protected void onDraw(Canvas canvas) {
    717         super.onDraw(canvas);
    718 
    719         // Draw the switch
    720         int switchLeft = mSwitchLeft;
    721         int switchTop = mSwitchTop;
    722         int switchRight = mSwitchRight;
    723         int switchBottom = mSwitchBottom;
    724 
    725         mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
    726         mTrackDrawable.draw(canvas);
    727 
    728         canvas.save();
    729 
    730         mTrackDrawable.getPadding(mTempRect);
    731         int switchInnerLeft = switchLeft + mTempRect.left;
    732         int switchInnerTop = switchTop + mTempRect.top;
    733         int switchInnerRight = switchRight - mTempRect.right;
    734         int switchInnerBottom = switchBottom - mTempRect.bottom;
    735         canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
    736 
    737         mThumbDrawable.getPadding(mTempRect);
    738         final int thumbPos = (int) (mThumbPosition + 0.5f);
    739         int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
    740         int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
    741 
    742         mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
    743         mThumbDrawable.draw(canvas);
    744 
    745         // mTextColors should not be null, but just in case
    746         if (mTextColors != null) {
    747             mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
    748                     mTextColors.getDefaultColor()));
    749         }
    750         mTextPaint.drawableState = getDrawableState();
    751 
    752         Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
    753         if (switchText != null) {
    754             canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
    755                     (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
    756             switchText.draw(canvas);
    757         }
    758 
    759         canvas.restore();
    760     }
    761 
    762     @Override
    763     public int getCompoundPaddingLeft() {
    764         if (!isLayoutRtl()) {
    765             return super.getCompoundPaddingLeft();
    766         }
    767         int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
    768         if (!TextUtils.isEmpty(getText())) {
    769             padding += mSwitchPadding;
    770         }
    771         return padding;
    772     }
    773 
    774     @Override
    775     public int getCompoundPaddingRight() {
    776         if (isLayoutRtl()) {
    777             return super.getCompoundPaddingRight();
    778         }
    779         int padding = super.getCompoundPaddingRight() + mSwitchWidth;
    780         if (!TextUtils.isEmpty(getText())) {
    781             padding += mSwitchPadding;
    782         }
    783         return padding;
    784     }
    785 
    786     private int getThumbScrollRange() {
    787         if (mTrackDrawable == null) {
    788             return 0;
    789         }
    790         mTrackDrawable.getPadding(mTempRect);
    791         return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
    792     }
    793 
    794     @Override
    795     protected int[] onCreateDrawableState(int extraSpace) {
    796         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    797         if (isChecked()) {
    798             mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    799         }
    800         return drawableState;
    801     }
    802 
    803     @Override
    804     protected void drawableStateChanged() {
    805         super.drawableStateChanged();
    806 
    807         int[] myDrawableState = getDrawableState();
    808 
    809         // Set the state of the Drawable
    810         // Drawable may be null when checked state is set from XML, from super constructor
    811         if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
    812         if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
    813 
    814         invalidate();
    815     }
    816 
    817     @Override
    818     protected boolean verifyDrawable(Drawable who) {
    819         return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
    820     }
    821 
    822     @Override
    823     public void jumpDrawablesToCurrentState() {
    824         super.jumpDrawablesToCurrentState();
    825         mThumbDrawable.jumpToCurrentState();
    826         mTrackDrawable.jumpToCurrentState();
    827     }
    828 
    829     @Override
    830     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    831         super.onInitializeAccessibilityEvent(event);
    832         event.setClassName(Switch.class.getName());
    833     }
    834 
    835     @Override
    836     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    837         super.onInitializeAccessibilityNodeInfo(info);
    838         info.setClassName(Switch.class.getName());
    839         CharSequence switchText = isChecked() ? mTextOn : mTextOff;
    840         if (!TextUtils.isEmpty(switchText)) {
    841             CharSequence oldText = info.getText();
    842             if (TextUtils.isEmpty(oldText)) {
    843                 info.setText(switchText);
    844             } else {
    845                 StringBuilder newText = new StringBuilder();
    846                 newText.append(oldText).append(' ').append(switchText);
    847                 info.setText(newText);
    848             }
    849         }
    850     }
    851 }
    852