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 */ Switch(Context context)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 */ Switch(Context context, AttributeSet attrs)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 */ Switch(Context context, AttributeSet attrs, int defStyle)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 */ setSwitchTextAppearance(Context context, int resid)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 setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex)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 */ setSwitchTypeface(Typeface tf, int style)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 */ setSwitchTypeface(Typeface tf)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 */ setSwitchPadding(int pixels)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 */ getSwitchPadding()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 */ setSwitchMinWidth(int pixels)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 */ getSwitchMinWidth()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 */ setThumbTextPadding(int pixels)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 */ getThumbTextPadding()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 */ setTrackDrawable(Drawable track)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 */ setTrackResource(int resId)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 */ getTrackDrawable()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 */ setThumbDrawable(Drawable thumb)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 */ setThumbResource(int resId)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 */ getThumbDrawable()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 */ getTextOn()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 */ setTextOn(CharSequence textOn)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 */ getTextOff()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 */ setTextOff(CharSequence textOff)476 public void setTextOff(CharSequence textOff) { 477 mTextOff = textOff; 478 requestLayout(); 479 } 480 481 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 onPopulateAccessibilityEvent(AccessibilityEvent event)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 makeLayout(CharSequence text)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 */ hitThumb(float x, float y)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 onTouchEvent(MotionEvent ev)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 cancelSuperTouch(MotionEvent ev)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 */ stopDrag(MotionEvent ev)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 animateThumbToCheckedState(boolean newCheckedState)641 private void animateThumbToCheckedState(boolean newCheckedState) { 642 // TODO animate! 643 //float targetPos = newCheckedState ? 0 : getThumbScrollRange(); 644 //mThumbPosition = targetPos; 645 setChecked(newCheckedState); 646 } 647 getTargetCheckedState()648 private boolean getTargetCheckedState() { 649 if (isLayoutRtl()) { 650 return mThumbPosition <= getThumbScrollRange() / 2; 651 } else { 652 return mThumbPosition >= getThumbScrollRange() / 2; 653 } 654 } 655 setThumbPosition(boolean checked)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 setChecked(boolean checked)665 public void setChecked(boolean checked) { 666 super.setChecked(checked); 667 setThumbPosition(isChecked()); 668 invalidate(); 669 } 670 671 @Override onLayout(boolean changed, int left, int top, int right, int bottom)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 onDraw(Canvas canvas)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 getCompoundPaddingLeft()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 getCompoundPaddingRight()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 getThumbScrollRange()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 onCreateDrawableState(int extraSpace)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 drawableStateChanged()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 verifyDrawable(Drawable who)818 protected boolean verifyDrawable(Drawable who) { 819 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; 820 } 821 822 @Override jumpDrawablesToCurrentState()823 public void jumpDrawablesToCurrentState() { 824 super.jumpDrawablesToCurrentState(); 825 mThumbDrawable.jumpToCurrentState(); 826 mTrackDrawable.jumpToCurrentState(); 827 } 828 829 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)830 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 831 super.onInitializeAccessibilityEvent(event); 832 event.setClassName(Switch.class.getName()); 833 } 834 835 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)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