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