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