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