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