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.animation.ObjectAnimator; 20 import android.annotation.DrawableRes; 21 import android.annotation.Nullable; 22 import android.annotation.StyleRes; 23 import android.content.Context; 24 import android.content.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.Canvas; 28 import android.graphics.Insets; 29 import android.graphics.Paint; 30 import android.graphics.PorterDuff; 31 import android.graphics.Rect; 32 import android.graphics.Typeface; 33 import android.graphics.Region.Op; 34 import android.graphics.drawable.Drawable; 35 import android.text.Layout; 36 import android.text.StaticLayout; 37 import android.text.TextPaint; 38 import android.text.TextUtils; 39 import android.text.method.AllCapsTransformationMethod; 40 import android.text.method.TransformationMethod2; 41 import android.util.AttributeSet; 42 import android.util.FloatProperty; 43 import android.util.MathUtils; 44 import android.view.Gravity; 45 import android.view.MotionEvent; 46 import android.view.SoundEffectConstants; 47 import android.view.VelocityTracker; 48 import android.view.ViewStructure; 49 import android.view.ViewConfiguration; 50 import android.view.accessibility.AccessibilityEvent; 51 import android.view.accessibility.AccessibilityNodeInfo; 52 53 import com.android.internal.R; 54 55 /** 56 * A Switch is a two-state toggle switch widget that can select between two 57 * options. The user may drag the "thumb" back and forth to choose the selected option, 58 * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text} 59 * property controls the text displayed in the label for the switch, whereas the 60 * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text 61 * controls the text on the thumb. Similarly, the 62 * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related 63 * setTypeface() methods control the typeface and style of label text, whereas the 64 * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and 65 * the related setSwitchTypeface() methods control that of the thumb. 66 * 67 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a> 68 * guide.</p> 69 * 70 * @attr ref android.R.styleable#Switch_textOn 71 * @attr ref android.R.styleable#Switch_textOff 72 * @attr ref android.R.styleable#Switch_switchMinWidth 73 * @attr ref android.R.styleable#Switch_switchPadding 74 * @attr ref android.R.styleable#Switch_switchTextAppearance 75 * @attr ref android.R.styleable#Switch_thumb 76 * @attr ref android.R.styleable#Switch_thumbTextPadding 77 * @attr ref android.R.styleable#Switch_track 78 */ 79 public class Switch extends CompoundButton { 80 private static final int THUMB_ANIMATION_DURATION = 250; 81 82 private static final int TOUCH_MODE_IDLE = 0; 83 private static final int TOUCH_MODE_DOWN = 1; 84 private static final int TOUCH_MODE_DRAGGING = 2; 85 86 // Enum for the "typeface" XML parameter. 87 private static final int SANS = 1; 88 private static final int SERIF = 2; 89 private static final int MONOSPACE = 3; 90 91 private Drawable mThumbDrawable; 92 private ColorStateList mThumbTintList = null; 93 private PorterDuff.Mode mThumbTintMode = null; 94 private boolean mHasThumbTint = false; 95 private boolean mHasThumbTintMode = false; 96 97 private Drawable mTrackDrawable; 98 private ColorStateList mTrackTintList = null; 99 private PorterDuff.Mode mTrackTintMode = null; 100 private boolean mHasTrackTint = false; 101 private boolean mHasTrackTintMode = false; 102 103 private int mThumbTextPadding; 104 private int mSwitchMinWidth; 105 private int mSwitchPadding; 106 private boolean mSplitTrack; 107 private CharSequence mTextOn; 108 private CharSequence mTextOff; 109 private boolean mShowText; 110 111 private int mTouchMode; 112 private int mTouchSlop; 113 private float mTouchX; 114 private float mTouchY; 115 private VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 116 private int mMinFlingVelocity; 117 118 private float mThumbPosition; 119 120 /** 121 * Width required to draw the switch track and thumb. Includes padding and 122 * optical bounds for both the track and thumb. 123 */ 124 private int mSwitchWidth; 125 126 /** 127 * Height required to draw the switch track and thumb. Includes padding and 128 * optical bounds for both the track and thumb. 129 */ 130 private int mSwitchHeight; 131 132 /** 133 * Width of the thumb's content region. Does not include padding or 134 * optical bounds. 135 */ 136 private int mThumbWidth; 137 138 /** Left bound for drawing the switch track and thumb. */ 139 private int mSwitchLeft; 140 141 /** Top bound for drawing the switch track and thumb. */ 142 private int mSwitchTop; 143 144 /** Right bound for drawing the switch track and thumb. */ 145 private int mSwitchRight; 146 147 /** Bottom bound for drawing the switch track and thumb. */ 148 private int mSwitchBottom; 149 150 private TextPaint mTextPaint; 151 private ColorStateList mTextColors; 152 private Layout mOnLayout; 153 private Layout mOffLayout; 154 private TransformationMethod2 mSwitchTransformationMethod; 155 private ObjectAnimator mPositionAnimator; 156 157 @SuppressWarnings("hiding") 158 private final Rect mTempRect = new Rect(); 159 160 private static final int[] CHECKED_STATE_SET = { 161 R.attr.state_checked 162 }; 163 164 /** 165 * Construct a new Switch with default styling. 166 * 167 * @param context The Context that will determine this widget's theming. 168 */ Switch(Context context)169 public Switch(Context context) { 170 this(context, null); 171 } 172 173 /** 174 * Construct a new Switch with default styling, overriding specific style 175 * attributes as requested. 176 * 177 * @param context The Context that will determine this widget's theming. 178 * @param attrs Specification of attributes that should deviate from default styling. 179 */ Switch(Context context, AttributeSet attrs)180 public Switch(Context context, AttributeSet attrs) { 181 this(context, attrs, com.android.internal.R.attr.switchStyle); 182 } 183 184 /** 185 * Construct a new Switch with a default style determined by the given theme attribute, 186 * overriding specific style attributes as requested. 187 * 188 * @param context The Context that will determine this widget's theming. 189 * @param attrs Specification of attributes that should deviate from the default styling. 190 * @param defStyleAttr An attribute in the current theme that contains a 191 * reference to a style resource that supplies default values for 192 * the view. Can be 0 to not look for defaults. 193 */ Switch(Context context, AttributeSet attrs, int defStyleAttr)194 public Switch(Context context, AttributeSet attrs, int defStyleAttr) { 195 this(context, attrs, defStyleAttr, 0); 196 } 197 198 199 /** 200 * Construct a new Switch with a default style determined by the given theme 201 * attribute or style resource, overriding specific style attributes as 202 * requested. 203 * 204 * @param context The Context that will determine this widget's theming. 205 * @param attrs Specification of attributes that should deviate from the 206 * default styling. 207 * @param defStyleAttr An attribute in the current theme that contains a 208 * reference to a style resource that supplies default values for 209 * the view. Can be 0 to not look for defaults. 210 * @param defStyleRes A resource identifier of a style resource that 211 * supplies default values for the view, used only if 212 * defStyleAttr is 0 or can not be found in the theme. Can be 0 213 * to not look for defaults. 214 */ Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)215 public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 216 super(context, attrs, defStyleAttr, defStyleRes); 217 218 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 219 220 final Resources res = getResources(); 221 mTextPaint.density = res.getDisplayMetrics().density; 222 mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale); 223 224 final TypedArray a = context.obtainStyledAttributes( 225 attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes); 226 mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb); 227 if (mThumbDrawable != null) { 228 mThumbDrawable.setCallback(this); 229 } 230 mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track); 231 if (mTrackDrawable != null) { 232 mTrackDrawable.setCallback(this); 233 } 234 mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn); 235 mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff); 236 mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true); 237 mThumbTextPadding = a.getDimensionPixelSize( 238 com.android.internal.R.styleable.Switch_thumbTextPadding, 0); 239 mSwitchMinWidth = a.getDimensionPixelSize( 240 com.android.internal.R.styleable.Switch_switchMinWidth, 0); 241 mSwitchPadding = a.getDimensionPixelSize( 242 com.android.internal.R.styleable.Switch_switchPadding, 0); 243 mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false); 244 245 ColorStateList thumbTintList = a.getColorStateList( 246 com.android.internal.R.styleable.Switch_thumbTint); 247 if (thumbTintList != null) { 248 mThumbTintList = thumbTintList; 249 mHasThumbTint = true; 250 } 251 PorterDuff.Mode thumbTintMode = Drawable.parseTintMode( 252 a.getInt(com.android.internal.R.styleable.Switch_thumbTintMode, -1), null); 253 if (mThumbTintMode != thumbTintMode) { 254 mThumbTintMode = thumbTintMode; 255 mHasThumbTintMode = true; 256 } 257 if (mHasThumbTint || mHasThumbTintMode) { 258 applyThumbTint(); 259 } 260 261 ColorStateList trackTintList = a.getColorStateList( 262 com.android.internal.R.styleable.Switch_trackTint); 263 if (trackTintList != null) { 264 mTrackTintList = trackTintList; 265 mHasTrackTint = true; 266 } 267 PorterDuff.Mode trackTintMode = Drawable.parseTintMode( 268 a.getInt(com.android.internal.R.styleable.Switch_trackTintMode, -1), null); 269 if (mTrackTintMode != trackTintMode) { 270 mTrackTintMode = trackTintMode; 271 mHasTrackTintMode = true; 272 } 273 if (mHasTrackTint || mHasTrackTintMode) { 274 applyTrackTint(); 275 } 276 277 final int appearance = a.getResourceId( 278 com.android.internal.R.styleable.Switch_switchTextAppearance, 0); 279 if (appearance != 0) { 280 setSwitchTextAppearance(context, appearance); 281 } 282 a.recycle(); 283 284 final ViewConfiguration config = ViewConfiguration.get(context); 285 mTouchSlop = config.getScaledTouchSlop(); 286 mMinFlingVelocity = config.getScaledMinimumFlingVelocity(); 287 288 // Refresh display with current params 289 refreshDrawableState(); 290 setChecked(isChecked()); 291 } 292 293 /** 294 * Sets the switch text color, size, style, hint color, and highlight color 295 * from the specified TextAppearance resource. 296 * 297 * @attr ref android.R.styleable#Switch_switchTextAppearance 298 */ setSwitchTextAppearance(Context context, @StyleRes int resid)299 public void setSwitchTextAppearance(Context context, @StyleRes int resid) { 300 TypedArray appearance = 301 context.obtainStyledAttributes(resid, 302 com.android.internal.R.styleable.TextAppearance); 303 304 ColorStateList colors; 305 int ts; 306 307 colors = appearance.getColorStateList(com.android.internal.R.styleable. 308 TextAppearance_textColor); 309 if (colors != null) { 310 mTextColors = colors; 311 } else { 312 // If no color set in TextAppearance, default to the view's textColor 313 mTextColors = getTextColors(); 314 } 315 316 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 317 TextAppearance_textSize, 0); 318 if (ts != 0) { 319 if (ts != mTextPaint.getTextSize()) { 320 mTextPaint.setTextSize(ts); 321 requestLayout(); 322 } 323 } 324 325 int typefaceIndex, styleIndex; 326 327 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 328 TextAppearance_typeface, -1); 329 styleIndex = appearance.getInt(com.android.internal.R.styleable. 330 TextAppearance_textStyle, -1); 331 332 setSwitchTypefaceByIndex(typefaceIndex, styleIndex); 333 334 boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable. 335 TextAppearance_textAllCaps, false); 336 if (allCaps) { 337 mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext()); 338 mSwitchTransformationMethod.setLengthChangesAllowed(true); 339 } else { 340 mSwitchTransformationMethod = null; 341 } 342 343 appearance.recycle(); 344 } 345 setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex)346 private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) { 347 Typeface tf = null; 348 switch (typefaceIndex) { 349 case SANS: 350 tf = Typeface.SANS_SERIF; 351 break; 352 353 case SERIF: 354 tf = Typeface.SERIF; 355 break; 356 357 case MONOSPACE: 358 tf = Typeface.MONOSPACE; 359 break; 360 } 361 362 setSwitchTypeface(tf, styleIndex); 363 } 364 365 /** 366 * Sets the typeface and style in which the text should be displayed on the 367 * switch, and turns on the fake bold and italic bits in the Paint if the 368 * Typeface that you provided does not have all the bits in the 369 * style that you specified. 370 */ setSwitchTypeface(Typeface tf, int style)371 public void setSwitchTypeface(Typeface tf, int style) { 372 if (style > 0) { 373 if (tf == null) { 374 tf = Typeface.defaultFromStyle(style); 375 } else { 376 tf = Typeface.create(tf, style); 377 } 378 379 setSwitchTypeface(tf); 380 // now compute what (if any) algorithmic styling is needed 381 int typefaceStyle = tf != null ? tf.getStyle() : 0; 382 int need = style & ~typefaceStyle; 383 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 384 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 385 } else { 386 mTextPaint.setFakeBoldText(false); 387 mTextPaint.setTextSkewX(0); 388 setSwitchTypeface(tf); 389 } 390 } 391 392 /** 393 * Sets the typeface in which the text should be displayed on the switch. 394 * Note that not all Typeface families actually have bold and italic 395 * variants, so you may need to use 396 * {@link #setSwitchTypeface(Typeface, int)} to get the appearance 397 * that you actually want. 398 * 399 * @attr ref android.R.styleable#TextView_typeface 400 * @attr ref android.R.styleable#TextView_textStyle 401 */ setSwitchTypeface(Typeface tf)402 public void setSwitchTypeface(Typeface tf) { 403 if (mTextPaint.getTypeface() != tf) { 404 mTextPaint.setTypeface(tf); 405 406 requestLayout(); 407 invalidate(); 408 } 409 } 410 411 /** 412 * Set the amount of horizontal padding between the switch and the associated text. 413 * 414 * @param pixels Amount of padding in pixels 415 * 416 * @attr ref android.R.styleable#Switch_switchPadding 417 */ setSwitchPadding(int pixels)418 public void setSwitchPadding(int pixels) { 419 mSwitchPadding = pixels; 420 requestLayout(); 421 } 422 423 /** 424 * Get the amount of horizontal padding between the switch and the associated text. 425 * 426 * @return Amount of padding in pixels 427 * 428 * @attr ref android.R.styleable#Switch_switchPadding 429 */ getSwitchPadding()430 public int getSwitchPadding() { 431 return mSwitchPadding; 432 } 433 434 /** 435 * Set the minimum width of the switch in pixels. The switch's width will be the maximum 436 * of this value and its measured width as determined by the switch drawables and text used. 437 * 438 * @param pixels Minimum width of the switch in pixels 439 * 440 * @attr ref android.R.styleable#Switch_switchMinWidth 441 */ setSwitchMinWidth(int pixels)442 public void setSwitchMinWidth(int pixels) { 443 mSwitchMinWidth = pixels; 444 requestLayout(); 445 } 446 447 /** 448 * Get the minimum width of the switch in pixels. The switch's width will be the maximum 449 * of this value and its measured width as determined by the switch drawables and text used. 450 * 451 * @return Minimum width of the switch in pixels 452 * 453 * @attr ref android.R.styleable#Switch_switchMinWidth 454 */ getSwitchMinWidth()455 public int getSwitchMinWidth() { 456 return mSwitchMinWidth; 457 } 458 459 /** 460 * Set the horizontal padding around the text drawn on the switch itself. 461 * 462 * @param pixels Horizontal padding for switch thumb text in pixels 463 * 464 * @attr ref android.R.styleable#Switch_thumbTextPadding 465 */ setThumbTextPadding(int pixels)466 public void setThumbTextPadding(int pixels) { 467 mThumbTextPadding = pixels; 468 requestLayout(); 469 } 470 471 /** 472 * Get the horizontal padding around the text drawn on the switch itself. 473 * 474 * @return Horizontal padding for switch thumb text in pixels 475 * 476 * @attr ref android.R.styleable#Switch_thumbTextPadding 477 */ getThumbTextPadding()478 public int getThumbTextPadding() { 479 return mThumbTextPadding; 480 } 481 482 /** 483 * Set the drawable used for the track that the switch slides within. 484 * 485 * @param track Track drawable 486 * 487 * @attr ref android.R.styleable#Switch_track 488 */ setTrackDrawable(Drawable track)489 public void setTrackDrawable(Drawable track) { 490 if (mTrackDrawable != null) { 491 mTrackDrawable.setCallback(null); 492 } 493 mTrackDrawable = track; 494 if (track != null) { 495 track.setCallback(this); 496 } 497 requestLayout(); 498 } 499 500 /** 501 * Set the drawable used for the track that the switch slides within. 502 * 503 * @param resId Resource ID of a track drawable 504 * 505 * @attr ref android.R.styleable#Switch_track 506 */ setTrackResource(@rawableRes int resId)507 public void setTrackResource(@DrawableRes int resId) { 508 setTrackDrawable(getContext().getDrawable(resId)); 509 } 510 511 /** 512 * Get the drawable used for the track that the switch slides within. 513 * 514 * @return Track drawable 515 * 516 * @attr ref android.R.styleable#Switch_track 517 */ getTrackDrawable()518 public Drawable getTrackDrawable() { 519 return mTrackDrawable; 520 } 521 522 /** 523 * Applies a tint to the track drawable. Does not modify the current 524 * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 525 * <p> 526 * Subsequent calls to {@link #setTrackDrawable(Drawable)} will 527 * automatically mutate the drawable and apply the specified tint and tint 528 * mode using {@link Drawable#setTintList(ColorStateList)}. 529 * 530 * @param tint the tint to apply, may be {@code null} to clear tint 531 * 532 * @attr ref android.R.styleable#Switch_trackTint 533 * @see #getTrackTintList() 534 * @see Drawable#setTintList(ColorStateList) 535 */ setTrackTintList(@ullable ColorStateList tint)536 public void setTrackTintList(@Nullable ColorStateList tint) { 537 mTrackTintList = tint; 538 mHasTrackTint = true; 539 540 applyTrackTint(); 541 } 542 543 /** 544 * @return the tint applied to the track drawable 545 * @attr ref android.R.styleable#Switch_trackTint 546 * @see #setTrackTintList(ColorStateList) 547 */ 548 @Nullable getTrackTintList()549 public ColorStateList getTrackTintList() { 550 return mTrackTintList; 551 } 552 553 /** 554 * Specifies the blending mode used to apply the tint specified by 555 * {@link #setTrackTintList(ColorStateList)}} to the track drawable. 556 * The default mode is {@link PorterDuff.Mode#SRC_IN}. 557 * 558 * @param tintMode the blending mode used to apply the tint, may be 559 * {@code null} to clear tint 560 * @attr ref android.R.styleable#Switch_trackTintMode 561 * @see #getTrackTintMode() 562 * @see Drawable#setTintMode(PorterDuff.Mode) 563 */ setTrackTintMode(@ullable PorterDuff.Mode tintMode)564 public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) { 565 mTrackTintMode = tintMode; 566 mHasTrackTintMode = true; 567 568 applyTrackTint(); 569 } 570 571 /** 572 * @return the blending mode used to apply the tint to the track 573 * drawable 574 * @attr ref android.R.styleable#Switch_trackTintMode 575 * @see #setTrackTintMode(PorterDuff.Mode) 576 */ 577 @Nullable getTrackTintMode()578 public PorterDuff.Mode getTrackTintMode() { 579 return mTrackTintMode; 580 } 581 applyTrackTint()582 private void applyTrackTint() { 583 if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) { 584 mTrackDrawable = mTrackDrawable.mutate(); 585 586 if (mHasTrackTint) { 587 mTrackDrawable.setTintList(mTrackTintList); 588 } 589 590 if (mHasTrackTintMode) { 591 mTrackDrawable.setTintMode(mTrackTintMode); 592 } 593 594 // The drawable (or one of its children) may not have been 595 // stateful before applying the tint, so let's try again. 596 if (mTrackDrawable.isStateful()) { 597 mTrackDrawable.setState(getDrawableState()); 598 } 599 } 600 } 601 602 /** 603 * Set the drawable used for the switch "thumb" - the piece that the user 604 * can physically touch and drag along the track. 605 * 606 * @param thumb Thumb drawable 607 * 608 * @attr ref android.R.styleable#Switch_thumb 609 */ setThumbDrawable(Drawable thumb)610 public void setThumbDrawable(Drawable thumb) { 611 if (mThumbDrawable != null) { 612 mThumbDrawable.setCallback(null); 613 } 614 mThumbDrawable = thumb; 615 if (thumb != null) { 616 thumb.setCallback(this); 617 } 618 requestLayout(); 619 } 620 621 /** 622 * Set the drawable used for the switch "thumb" - the piece that the user 623 * can physically touch and drag along the track. 624 * 625 * @param resId Resource ID of a thumb drawable 626 * 627 * @attr ref android.R.styleable#Switch_thumb 628 */ setThumbResource(@rawableRes int resId)629 public void setThumbResource(@DrawableRes int resId) { 630 setThumbDrawable(getContext().getDrawable(resId)); 631 } 632 633 /** 634 * Get the drawable used for the switch "thumb" - the piece that the user 635 * can physically touch and drag along the track. 636 * 637 * @return Thumb drawable 638 * 639 * @attr ref android.R.styleable#Switch_thumb 640 */ getThumbDrawable()641 public Drawable getThumbDrawable() { 642 return mThumbDrawable; 643 } 644 645 /** 646 * Applies a tint to the thumb drawable. Does not modify the current 647 * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 648 * <p> 649 * Subsequent calls to {@link #setThumbDrawable(Drawable)} will 650 * automatically mutate the drawable and apply the specified tint and tint 651 * mode using {@link Drawable#setTintList(ColorStateList)}. 652 * 653 * @param tint the tint to apply, may be {@code null} to clear tint 654 * 655 * @attr ref android.R.styleable#Switch_thumbTint 656 * @see #getThumbTintList() 657 * @see Drawable#setTintList(ColorStateList) 658 */ setThumbTintList(@ullable ColorStateList tint)659 public void setThumbTintList(@Nullable ColorStateList tint) { 660 mThumbTintList = tint; 661 mHasThumbTint = true; 662 663 applyThumbTint(); 664 } 665 666 /** 667 * @return the tint applied to the thumb drawable 668 * @attr ref android.R.styleable#Switch_thumbTint 669 * @see #setThumbTintList(ColorStateList) 670 */ 671 @Nullable getThumbTintList()672 public ColorStateList getThumbTintList() { 673 return mThumbTintList; 674 } 675 676 /** 677 * Specifies the blending mode used to apply the tint specified by 678 * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. 679 * The default mode is {@link PorterDuff.Mode#SRC_IN}. 680 * 681 * @param tintMode the blending mode used to apply the tint, may be 682 * {@code null} to clear tint 683 * @attr ref android.R.styleable#Switch_thumbTintMode 684 * @see #getThumbTintMode() 685 * @see Drawable#setTintMode(PorterDuff.Mode) 686 */ setThumbTintMode(@ullable PorterDuff.Mode tintMode)687 public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { 688 mThumbTintMode = tintMode; 689 mHasThumbTintMode = true; 690 691 applyThumbTint(); 692 } 693 694 /** 695 * @return the blending mode used to apply the tint to the thumb 696 * drawable 697 * @attr ref android.R.styleable#Switch_thumbTintMode 698 * @see #setThumbTintMode(PorterDuff.Mode) 699 */ 700 @Nullable getThumbTintMode()701 public PorterDuff.Mode getThumbTintMode() { 702 return mThumbTintMode; 703 } 704 applyThumbTint()705 private void applyThumbTint() { 706 if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) { 707 mThumbDrawable = mThumbDrawable.mutate(); 708 709 if (mHasThumbTint) { 710 mThumbDrawable.setTintList(mThumbTintList); 711 } 712 713 if (mHasThumbTintMode) { 714 mThumbDrawable.setTintMode(mThumbTintMode); 715 } 716 717 // The drawable (or one of its children) may not have been 718 // stateful before applying the tint, so let's try again. 719 if (mThumbDrawable.isStateful()) { 720 mThumbDrawable.setState(getDrawableState()); 721 } 722 } 723 } 724 725 /** 726 * Specifies whether the track should be split by the thumb. When true, 727 * the thumb's optical bounds will be clipped out of the track drawable, 728 * then the thumb will be drawn into the resulting gap. 729 * 730 * @param splitTrack Whether the track should be split by the thumb 731 * 732 * @attr ref android.R.styleable#Switch_splitTrack 733 */ setSplitTrack(boolean splitTrack)734 public void setSplitTrack(boolean splitTrack) { 735 mSplitTrack = splitTrack; 736 invalidate(); 737 } 738 739 /** 740 * Returns whether the track should be split by the thumb. 741 * 742 * @attr ref android.R.styleable#Switch_splitTrack 743 */ getSplitTrack()744 public boolean getSplitTrack() { 745 return mSplitTrack; 746 } 747 748 /** 749 * Returns the text displayed when the button is in the checked state. 750 * 751 * @attr ref android.R.styleable#Switch_textOn 752 */ getTextOn()753 public CharSequence getTextOn() { 754 return mTextOn; 755 } 756 757 /** 758 * Sets the text displayed when the button is in the checked state. 759 * 760 * @attr ref android.R.styleable#Switch_textOn 761 */ setTextOn(CharSequence textOn)762 public void setTextOn(CharSequence textOn) { 763 mTextOn = textOn; 764 requestLayout(); 765 } 766 767 /** 768 * Returns the text displayed when the button is not in the checked state. 769 * 770 * @attr ref android.R.styleable#Switch_textOff 771 */ getTextOff()772 public CharSequence getTextOff() { 773 return mTextOff; 774 } 775 776 /** 777 * Sets the text displayed when the button is not in the checked state. 778 * 779 * @attr ref android.R.styleable#Switch_textOff 780 */ setTextOff(CharSequence textOff)781 public void setTextOff(CharSequence textOff) { 782 mTextOff = textOff; 783 requestLayout(); 784 } 785 786 /** 787 * Sets whether the on/off text should be displayed. 788 * 789 * @param showText {@code true} to display on/off text 790 * @attr ref android.R.styleable#Switch_showText 791 */ setShowText(boolean showText)792 public void setShowText(boolean showText) { 793 if (mShowText != showText) { 794 mShowText = showText; 795 requestLayout(); 796 } 797 } 798 799 /** 800 * @return whether the on/off text should be displayed 801 * @attr ref android.R.styleable#Switch_showText 802 */ getShowText()803 public boolean getShowText() { 804 return mShowText; 805 } 806 807 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)808 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 809 if (mShowText) { 810 if (mOnLayout == null) { 811 mOnLayout = makeLayout(mTextOn); 812 } 813 814 if (mOffLayout == null) { 815 mOffLayout = makeLayout(mTextOff); 816 } 817 } 818 819 final Rect padding = mTempRect; 820 final int thumbWidth; 821 final int thumbHeight; 822 if (mThumbDrawable != null) { 823 // Cached thumb width does not include padding. 824 mThumbDrawable.getPadding(padding); 825 thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right; 826 thumbHeight = mThumbDrawable.getIntrinsicHeight(); 827 } else { 828 thumbWidth = 0; 829 thumbHeight = 0; 830 } 831 832 final int maxTextWidth; 833 if (mShowText) { 834 maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()) 835 + mThumbTextPadding * 2; 836 } else { 837 maxTextWidth = 0; 838 } 839 840 mThumbWidth = Math.max(maxTextWidth, thumbWidth); 841 842 final int trackHeight; 843 if (mTrackDrawable != null) { 844 mTrackDrawable.getPadding(padding); 845 trackHeight = mTrackDrawable.getIntrinsicHeight(); 846 } else { 847 padding.setEmpty(); 848 trackHeight = 0; 849 } 850 851 // Adjust left and right padding to ensure there's enough room for the 852 // thumb's padding (when present). 853 int paddingLeft = padding.left; 854 int paddingRight = padding.right; 855 if (mThumbDrawable != null) { 856 final Insets inset = mThumbDrawable.getOpticalInsets(); 857 paddingLeft = Math.max(paddingLeft, inset.left); 858 paddingRight = Math.max(paddingRight, inset.right); 859 } 860 861 final int switchWidth = Math.max(mSwitchMinWidth, 862 2 * mThumbWidth + paddingLeft + paddingRight); 863 final int switchHeight = Math.max(trackHeight, thumbHeight); 864 mSwitchWidth = switchWidth; 865 mSwitchHeight = switchHeight; 866 867 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 868 869 final int measuredHeight = getMeasuredHeight(); 870 if (measuredHeight < switchHeight) { 871 setMeasuredDimension(getMeasuredWidthAndState(), switchHeight); 872 } 873 } 874 875 /** @hide */ 876 @Override onPopulateAccessibilityEventInternal(AccessibilityEvent event)877 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { 878 super.onPopulateAccessibilityEventInternal(event); 879 880 final CharSequence text = isChecked() ? mTextOn : mTextOff; 881 if (text != null) { 882 event.getText().add(text); 883 } 884 } 885 makeLayout(CharSequence text)886 private Layout makeLayout(CharSequence text) { 887 final CharSequence transformed = (mSwitchTransformationMethod != null) 888 ? mSwitchTransformationMethod.getTransformation(text, this) 889 : text; 890 891 return new StaticLayout(transformed, mTextPaint, 892 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)), 893 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true); 894 } 895 896 /** 897 * @return true if (x, y) is within the target area of the switch thumb 898 */ hitThumb(float x, float y)899 private boolean hitThumb(float x, float y) { 900 if (mThumbDrawable == null) { 901 return false; 902 } 903 904 // Relies on mTempRect, MUST be called first! 905 final int thumbOffset = getThumbOffset(); 906 907 mThumbDrawable.getPadding(mTempRect); 908 final int thumbTop = mSwitchTop - mTouchSlop; 909 final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop; 910 final int thumbRight = thumbLeft + mThumbWidth + 911 mTempRect.left + mTempRect.right + mTouchSlop; 912 final int thumbBottom = mSwitchBottom + mTouchSlop; 913 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom; 914 } 915 916 @Override onTouchEvent(MotionEvent ev)917 public boolean onTouchEvent(MotionEvent ev) { 918 mVelocityTracker.addMovement(ev); 919 final int action = ev.getActionMasked(); 920 switch (action) { 921 case MotionEvent.ACTION_DOWN: { 922 final float x = ev.getX(); 923 final float y = ev.getY(); 924 if (isEnabled() && hitThumb(x, y)) { 925 mTouchMode = TOUCH_MODE_DOWN; 926 mTouchX = x; 927 mTouchY = y; 928 } 929 break; 930 } 931 932 case MotionEvent.ACTION_MOVE: { 933 switch (mTouchMode) { 934 case TOUCH_MODE_IDLE: 935 // Didn't target the thumb, treat normally. 936 break; 937 938 case TOUCH_MODE_DOWN: { 939 final float x = ev.getX(); 940 final float y = ev.getY(); 941 if (Math.abs(x - mTouchX) > mTouchSlop || 942 Math.abs(y - mTouchY) > mTouchSlop) { 943 mTouchMode = TOUCH_MODE_DRAGGING; 944 getParent().requestDisallowInterceptTouchEvent(true); 945 mTouchX = x; 946 mTouchY = y; 947 return true; 948 } 949 break; 950 } 951 952 case TOUCH_MODE_DRAGGING: { 953 final float x = ev.getX(); 954 final int thumbScrollRange = getThumbScrollRange(); 955 final float thumbScrollOffset = x - mTouchX; 956 float dPos; 957 if (thumbScrollRange != 0) { 958 dPos = thumbScrollOffset / thumbScrollRange; 959 } else { 960 // If the thumb scroll range is empty, just use the 961 // movement direction to snap on or off. 962 dPos = thumbScrollOffset > 0 ? 1 : -1; 963 } 964 if (isLayoutRtl()) { 965 dPos = -dPos; 966 } 967 final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1); 968 if (newPos != mThumbPosition) { 969 mTouchX = x; 970 setThumbPosition(newPos); 971 } 972 return true; 973 } 974 } 975 break; 976 } 977 978 case MotionEvent.ACTION_UP: 979 case MotionEvent.ACTION_CANCEL: { 980 if (mTouchMode == TOUCH_MODE_DRAGGING) { 981 stopDrag(ev); 982 // Allow super class to handle pressed state, etc. 983 super.onTouchEvent(ev); 984 return true; 985 } 986 mTouchMode = TOUCH_MODE_IDLE; 987 mVelocityTracker.clear(); 988 break; 989 } 990 } 991 992 return super.onTouchEvent(ev); 993 } 994 cancelSuperTouch(MotionEvent ev)995 private void cancelSuperTouch(MotionEvent ev) { 996 MotionEvent cancel = MotionEvent.obtain(ev); 997 cancel.setAction(MotionEvent.ACTION_CANCEL); 998 super.onTouchEvent(cancel); 999 cancel.recycle(); 1000 } 1001 1002 /** 1003 * Called from onTouchEvent to end a drag operation. 1004 * 1005 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL 1006 */ stopDrag(MotionEvent ev)1007 private void stopDrag(MotionEvent ev) { 1008 mTouchMode = TOUCH_MODE_IDLE; 1009 1010 // Commit the change if the event is up and not canceled and the switch 1011 // has not been disabled during the drag. 1012 final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); 1013 final boolean oldState = isChecked(); 1014 final boolean newState; 1015 if (commitChange) { 1016 mVelocityTracker.computeCurrentVelocity(1000); 1017 final float xvel = mVelocityTracker.getXVelocity(); 1018 if (Math.abs(xvel) > mMinFlingVelocity) { 1019 newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0); 1020 } else { 1021 newState = getTargetCheckedState(); 1022 } 1023 } else { 1024 newState = oldState; 1025 } 1026 1027 if (newState != oldState) { 1028 playSoundEffect(SoundEffectConstants.CLICK); 1029 setChecked(newState); 1030 } 1031 1032 cancelSuperTouch(ev); 1033 } 1034 animateThumbToCheckedState(boolean newCheckedState)1035 private void animateThumbToCheckedState(boolean newCheckedState) { 1036 final float targetPosition = newCheckedState ? 1 : 0; 1037 mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition); 1038 mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION); 1039 mPositionAnimator.setAutoCancel(true); 1040 mPositionAnimator.start(); 1041 } 1042 cancelPositionAnimator()1043 private void cancelPositionAnimator() { 1044 if (mPositionAnimator != null) { 1045 mPositionAnimator.cancel(); 1046 } 1047 } 1048 getTargetCheckedState()1049 private boolean getTargetCheckedState() { 1050 return mThumbPosition > 0.5f; 1051 } 1052 1053 /** 1054 * Sets the thumb position as a decimal value between 0 (off) and 1 (on). 1055 * 1056 * @param position new position between [0,1] 1057 */ setThumbPosition(float position)1058 private void setThumbPosition(float position) { 1059 mThumbPosition = position; 1060 invalidate(); 1061 } 1062 1063 @Override toggle()1064 public void toggle() { 1065 setChecked(!isChecked()); 1066 } 1067 1068 @Override setChecked(boolean checked)1069 public void setChecked(boolean checked) { 1070 super.setChecked(checked); 1071 1072 // Calling the super method may result in setChecked() getting called 1073 // recursively with a different value, so load the REAL value... 1074 checked = isChecked(); 1075 1076 if (isAttachedToWindow() && isLaidOut()) { 1077 animateThumbToCheckedState(checked); 1078 } else { 1079 // Immediately move the thumb to the new position. 1080 cancelPositionAnimator(); 1081 setThumbPosition(checked ? 1 : 0); 1082 } 1083 } 1084 1085 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1086 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1087 super.onLayout(changed, left, top, right, bottom); 1088 1089 int opticalInsetLeft = 0; 1090 int opticalInsetRight = 0; 1091 if (mThumbDrawable != null) { 1092 final Rect trackPadding = mTempRect; 1093 if (mTrackDrawable != null) { 1094 mTrackDrawable.getPadding(trackPadding); 1095 } else { 1096 trackPadding.setEmpty(); 1097 } 1098 1099 final Insets insets = mThumbDrawable.getOpticalInsets(); 1100 opticalInsetLeft = Math.max(0, insets.left - trackPadding.left); 1101 opticalInsetRight = Math.max(0, insets.right - trackPadding.right); 1102 } 1103 1104 final int switchRight; 1105 final int switchLeft; 1106 if (isLayoutRtl()) { 1107 switchLeft = getPaddingLeft() + opticalInsetLeft; 1108 switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight; 1109 } else { 1110 switchRight = getWidth() - getPaddingRight() - opticalInsetRight; 1111 switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight; 1112 } 1113 1114 final int switchTop; 1115 final int switchBottom; 1116 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { 1117 default: 1118 case Gravity.TOP: 1119 switchTop = getPaddingTop(); 1120 switchBottom = switchTop + mSwitchHeight; 1121 break; 1122 1123 case Gravity.CENTER_VERTICAL: 1124 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 - 1125 mSwitchHeight / 2; 1126 switchBottom = switchTop + mSwitchHeight; 1127 break; 1128 1129 case Gravity.BOTTOM: 1130 switchBottom = getHeight() - getPaddingBottom(); 1131 switchTop = switchBottom - mSwitchHeight; 1132 break; 1133 } 1134 1135 mSwitchLeft = switchLeft; 1136 mSwitchTop = switchTop; 1137 mSwitchBottom = switchBottom; 1138 mSwitchRight = switchRight; 1139 } 1140 1141 @Override draw(Canvas c)1142 public void draw(Canvas c) { 1143 final Rect padding = mTempRect; 1144 final int switchLeft = mSwitchLeft; 1145 final int switchTop = mSwitchTop; 1146 final int switchRight = mSwitchRight; 1147 final int switchBottom = mSwitchBottom; 1148 1149 int thumbInitialLeft = switchLeft + getThumbOffset(); 1150 1151 final Insets thumbInsets; 1152 if (mThumbDrawable != null) { 1153 thumbInsets = mThumbDrawable.getOpticalInsets(); 1154 } else { 1155 thumbInsets = Insets.NONE; 1156 } 1157 1158 // Layout the track. 1159 if (mTrackDrawable != null) { 1160 mTrackDrawable.getPadding(padding); 1161 1162 // Adjust thumb position for track padding. 1163 thumbInitialLeft += padding.left; 1164 1165 // If necessary, offset by the optical insets of the thumb asset. 1166 int trackLeft = switchLeft; 1167 int trackTop = switchTop; 1168 int trackRight = switchRight; 1169 int trackBottom = switchBottom; 1170 if (thumbInsets != Insets.NONE) { 1171 if (thumbInsets.left > padding.left) { 1172 trackLeft += thumbInsets.left - padding.left; 1173 } 1174 if (thumbInsets.top > padding.top) { 1175 trackTop += thumbInsets.top - padding.top; 1176 } 1177 if (thumbInsets.right > padding.right) { 1178 trackRight -= thumbInsets.right - padding.right; 1179 } 1180 if (thumbInsets.bottom > padding.bottom) { 1181 trackBottom -= thumbInsets.bottom - padding.bottom; 1182 } 1183 } 1184 mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom); 1185 } 1186 1187 // Layout the thumb. 1188 if (mThumbDrawable != null) { 1189 mThumbDrawable.getPadding(padding); 1190 1191 final int thumbLeft = thumbInitialLeft - padding.left; 1192 final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right; 1193 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); 1194 1195 final Drawable background = getBackground(); 1196 if (background != null) { 1197 background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom); 1198 } 1199 } 1200 1201 // Draw the background. 1202 super.draw(c); 1203 } 1204 1205 @Override onDraw(Canvas canvas)1206 protected void onDraw(Canvas canvas) { 1207 super.onDraw(canvas); 1208 1209 final Rect padding = mTempRect; 1210 final Drawable trackDrawable = mTrackDrawable; 1211 if (trackDrawable != null) { 1212 trackDrawable.getPadding(padding); 1213 } else { 1214 padding.setEmpty(); 1215 } 1216 1217 final int switchTop = mSwitchTop; 1218 final int switchBottom = mSwitchBottom; 1219 final int switchInnerTop = switchTop + padding.top; 1220 final int switchInnerBottom = switchBottom - padding.bottom; 1221 1222 final Drawable thumbDrawable = mThumbDrawable; 1223 if (trackDrawable != null) { 1224 if (mSplitTrack && thumbDrawable != null) { 1225 final Insets insets = thumbDrawable.getOpticalInsets(); 1226 thumbDrawable.copyBounds(padding); 1227 padding.left += insets.left; 1228 padding.right -= insets.right; 1229 1230 final int saveCount = canvas.save(); 1231 canvas.clipRect(padding, Op.DIFFERENCE); 1232 trackDrawable.draw(canvas); 1233 canvas.restoreToCount(saveCount); 1234 } else { 1235 trackDrawable.draw(canvas); 1236 } 1237 } 1238 1239 final int saveCount = canvas.save(); 1240 1241 if (thumbDrawable != null) { 1242 thumbDrawable.draw(canvas); 1243 } 1244 1245 final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; 1246 if (switchText != null) { 1247 final int drawableState[] = getDrawableState(); 1248 if (mTextColors != null) { 1249 mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0)); 1250 } 1251 mTextPaint.drawableState = drawableState; 1252 1253 final int cX; 1254 if (thumbDrawable != null) { 1255 final Rect bounds = thumbDrawable.getBounds(); 1256 cX = bounds.left + bounds.right; 1257 } else { 1258 cX = getWidth(); 1259 } 1260 1261 final int left = cX / 2 - switchText.getWidth() / 2; 1262 final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2; 1263 canvas.translate(left, top); 1264 switchText.draw(canvas); 1265 } 1266 1267 canvas.restoreToCount(saveCount); 1268 } 1269 1270 @Override getCompoundPaddingLeft()1271 public int getCompoundPaddingLeft() { 1272 if (!isLayoutRtl()) { 1273 return super.getCompoundPaddingLeft(); 1274 } 1275 int padding = super.getCompoundPaddingLeft() + mSwitchWidth; 1276 if (!TextUtils.isEmpty(getText())) { 1277 padding += mSwitchPadding; 1278 } 1279 return padding; 1280 } 1281 1282 @Override getCompoundPaddingRight()1283 public int getCompoundPaddingRight() { 1284 if (isLayoutRtl()) { 1285 return super.getCompoundPaddingRight(); 1286 } 1287 int padding = super.getCompoundPaddingRight() + mSwitchWidth; 1288 if (!TextUtils.isEmpty(getText())) { 1289 padding += mSwitchPadding; 1290 } 1291 return padding; 1292 } 1293 1294 /** 1295 * Translates thumb position to offset according to current RTL setting and 1296 * thumb scroll range. Accounts for both track and thumb padding. 1297 * 1298 * @return thumb offset 1299 */ getThumbOffset()1300 private int getThumbOffset() { 1301 final float thumbPosition; 1302 if (isLayoutRtl()) { 1303 thumbPosition = 1 - mThumbPosition; 1304 } else { 1305 thumbPosition = mThumbPosition; 1306 } 1307 return (int) (thumbPosition * getThumbScrollRange() + 0.5f); 1308 } 1309 getThumbScrollRange()1310 private int getThumbScrollRange() { 1311 if (mTrackDrawable != null) { 1312 final Rect padding = mTempRect; 1313 mTrackDrawable.getPadding(padding); 1314 1315 final Insets insets; 1316 if (mThumbDrawable != null) { 1317 insets = mThumbDrawable.getOpticalInsets(); 1318 } else { 1319 insets = Insets.NONE; 1320 } 1321 1322 return mSwitchWidth - mThumbWidth - padding.left - padding.right 1323 - insets.left - insets.right; 1324 } else { 1325 return 0; 1326 } 1327 } 1328 1329 @Override onCreateDrawableState(int extraSpace)1330 protected int[] onCreateDrawableState(int extraSpace) { 1331 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1332 if (isChecked()) { 1333 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 1334 } 1335 return drawableState; 1336 } 1337 1338 @Override drawableStateChanged()1339 protected void drawableStateChanged() { 1340 super.drawableStateChanged(); 1341 1342 final int[] myDrawableState = getDrawableState(); 1343 1344 if (mThumbDrawable != null) { 1345 mThumbDrawable.setState(myDrawableState); 1346 } 1347 1348 if (mTrackDrawable != null) { 1349 mTrackDrawable.setState(myDrawableState); 1350 } 1351 1352 invalidate(); 1353 } 1354 1355 @Override drawableHotspotChanged(float x, float y)1356 public void drawableHotspotChanged(float x, float y) { 1357 super.drawableHotspotChanged(x, y); 1358 1359 if (mThumbDrawable != null) { 1360 mThumbDrawable.setHotspot(x, y); 1361 } 1362 1363 if (mTrackDrawable != null) { 1364 mTrackDrawable.setHotspot(x, y); 1365 } 1366 } 1367 1368 @Override verifyDrawable(Drawable who)1369 protected boolean verifyDrawable(Drawable who) { 1370 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; 1371 } 1372 1373 @Override jumpDrawablesToCurrentState()1374 public void jumpDrawablesToCurrentState() { 1375 super.jumpDrawablesToCurrentState(); 1376 1377 if (mThumbDrawable != null) { 1378 mThumbDrawable.jumpToCurrentState(); 1379 } 1380 1381 if (mTrackDrawable != null) { 1382 mTrackDrawable.jumpToCurrentState(); 1383 } 1384 1385 if (mPositionAnimator != null && mPositionAnimator.isRunning()) { 1386 mPositionAnimator.end(); 1387 mPositionAnimator = null; 1388 } 1389 } 1390 1391 @Override getAccessibilityClassName()1392 public CharSequence getAccessibilityClassName() { 1393 return Switch.class.getName(); 1394 } 1395 1396 @Override onProvideStructure(ViewStructure structure)1397 public void onProvideStructure(ViewStructure structure) { 1398 super.onProvideStructure(structure); 1399 CharSequence switchText = isChecked() ? mTextOn : mTextOff; 1400 if (!TextUtils.isEmpty(switchText)) { 1401 CharSequence oldText = structure.getText(); 1402 if (TextUtils.isEmpty(oldText)) { 1403 structure.setText(switchText); 1404 } else { 1405 StringBuilder newText = new StringBuilder(); 1406 newText.append(oldText).append(' ').append(switchText); 1407 structure.setText(newText); 1408 } 1409 // The style of the label text is provided via the base TextView class. This is more 1410 // relevant than the style of the (optional) on/off text on the switch button itself, 1411 // so ignore the size/color/style stored this.mTextPaint. 1412 } 1413 } 1414 1415 /** @hide */ 1416 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)1417 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1418 super.onInitializeAccessibilityNodeInfoInternal(info); 1419 CharSequence switchText = isChecked() ? mTextOn : mTextOff; 1420 if (!TextUtils.isEmpty(switchText)) { 1421 CharSequence oldText = info.getText(); 1422 if (TextUtils.isEmpty(oldText)) { 1423 info.setText(switchText); 1424 } else { 1425 StringBuilder newText = new StringBuilder(); 1426 newText.append(oldText).append(' ').append(switchText); 1427 info.setText(newText); 1428 } 1429 } 1430 } 1431 1432 private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") { 1433 @Override 1434 public Float get(Switch object) { 1435 return object.mThumbPosition; 1436 } 1437 1438 @Override 1439 public void setValue(Switch object, float value) { 1440 object.setThumbPosition(value); 1441 } 1442 }; 1443 } 1444