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.Typeface; 34 import android.graphics.Region.Op; 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.ViewStructure; 50 import android.view.ViewConfiguration; 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 return new StaticLayout(transformed, mTextPaint, 896 (int) Math.ceil(Layout.getDesiredWidth(transformed, mTextPaint)), 897 Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true); 898 } 899 900 /** 901 * @return true if (x, y) is within the target area of the switch thumb 902 */ 903 private boolean hitThumb(float x, float y) { 904 if (mThumbDrawable == null) { 905 return false; 906 } 907 908 // Relies on mTempRect, MUST be called first! 909 final int thumbOffset = getThumbOffset(); 910 911 mThumbDrawable.getPadding(mTempRect); 912 final int thumbTop = mSwitchTop - mTouchSlop; 913 final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop; 914 final int thumbRight = thumbLeft + mThumbWidth + 915 mTempRect.left + mTempRect.right + mTouchSlop; 916 final int thumbBottom = mSwitchBottom + mTouchSlop; 917 return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom; 918 } 919 920 @Override 921 public boolean onTouchEvent(MotionEvent ev) { 922 mVelocityTracker.addMovement(ev); 923 final int action = ev.getActionMasked(); 924 switch (action) { 925 case MotionEvent.ACTION_DOWN: { 926 final float x = ev.getX(); 927 final float y = ev.getY(); 928 if (isEnabled() && hitThumb(x, y)) { 929 mTouchMode = TOUCH_MODE_DOWN; 930 mTouchX = x; 931 mTouchY = y; 932 } 933 break; 934 } 935 936 case MotionEvent.ACTION_MOVE: { 937 switch (mTouchMode) { 938 case TOUCH_MODE_IDLE: 939 // Didn't target the thumb, treat normally. 940 break; 941 942 case TOUCH_MODE_DOWN: { 943 final float x = ev.getX(); 944 final float y = ev.getY(); 945 if (Math.abs(x - mTouchX) > mTouchSlop || 946 Math.abs(y - mTouchY) > mTouchSlop) { 947 mTouchMode = TOUCH_MODE_DRAGGING; 948 getParent().requestDisallowInterceptTouchEvent(true); 949 mTouchX = x; 950 mTouchY = y; 951 return true; 952 } 953 break; 954 } 955 956 case TOUCH_MODE_DRAGGING: { 957 final float x = ev.getX(); 958 final int thumbScrollRange = getThumbScrollRange(); 959 final float thumbScrollOffset = x - mTouchX; 960 float dPos; 961 if (thumbScrollRange != 0) { 962 dPos = thumbScrollOffset / thumbScrollRange; 963 } else { 964 // If the thumb scroll range is empty, just use the 965 // movement direction to snap on or off. 966 dPos = thumbScrollOffset > 0 ? 1 : -1; 967 } 968 if (isLayoutRtl()) { 969 dPos = -dPos; 970 } 971 final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1); 972 if (newPos != mThumbPosition) { 973 mTouchX = x; 974 setThumbPosition(newPos); 975 } 976 return true; 977 } 978 } 979 break; 980 } 981 982 case MotionEvent.ACTION_UP: 983 case MotionEvent.ACTION_CANCEL: { 984 if (mTouchMode == TOUCH_MODE_DRAGGING) { 985 stopDrag(ev); 986 // Allow super class to handle pressed state, etc. 987 super.onTouchEvent(ev); 988 return true; 989 } 990 mTouchMode = TOUCH_MODE_IDLE; 991 mVelocityTracker.clear(); 992 break; 993 } 994 } 995 996 return super.onTouchEvent(ev); 997 } 998 999 private void cancelSuperTouch(MotionEvent ev) { 1000 MotionEvent cancel = MotionEvent.obtain(ev); 1001 cancel.setAction(MotionEvent.ACTION_CANCEL); 1002 super.onTouchEvent(cancel); 1003 cancel.recycle(); 1004 } 1005 1006 /** 1007 * Called from onTouchEvent to end a drag operation. 1008 * 1009 * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL 1010 */ 1011 private void stopDrag(MotionEvent ev) { 1012 mTouchMode = TOUCH_MODE_IDLE; 1013 1014 // Commit the change if the event is up and not canceled and the switch 1015 // has not been disabled during the drag. 1016 final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled(); 1017 final boolean oldState = isChecked(); 1018 final boolean newState; 1019 if (commitChange) { 1020 mVelocityTracker.computeCurrentVelocity(1000); 1021 final float xvel = mVelocityTracker.getXVelocity(); 1022 if (Math.abs(xvel) > mMinFlingVelocity) { 1023 newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0); 1024 } else { 1025 newState = getTargetCheckedState(); 1026 } 1027 } else { 1028 newState = oldState; 1029 } 1030 1031 if (newState != oldState) { 1032 playSoundEffect(SoundEffectConstants.CLICK); 1033 } 1034 // Always call setChecked so that the thumb is moved back to the correct edge 1035 setChecked(newState); 1036 cancelSuperTouch(ev); 1037 } 1038 1039 private void animateThumbToCheckedState(boolean newCheckedState) { 1040 final float targetPosition = newCheckedState ? 1 : 0; 1041 mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition); 1042 mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION); 1043 mPositionAnimator.setAutoCancel(true); 1044 mPositionAnimator.start(); 1045 } 1046 1047 private void cancelPositionAnimator() { 1048 if (mPositionAnimator != null) { 1049 mPositionAnimator.cancel(); 1050 } 1051 } 1052 1053 private boolean getTargetCheckedState() { 1054 return mThumbPosition > 0.5f; 1055 } 1056 1057 /** 1058 * Sets the thumb position as a decimal value between 0 (off) and 1 (on). 1059 * 1060 * @param position new position between [0,1] 1061 */ 1062 private void setThumbPosition(float position) { 1063 mThumbPosition = position; 1064 invalidate(); 1065 } 1066 1067 @Override 1068 public void toggle() { 1069 setChecked(!isChecked()); 1070 } 1071 1072 @Override 1073 public void setChecked(boolean checked) { 1074 super.setChecked(checked); 1075 1076 // Calling the super method may result in setChecked() getting called 1077 // recursively with a different value, so load the REAL value... 1078 checked = isChecked(); 1079 1080 if (isAttachedToWindow() && isLaidOut()) { 1081 animateThumbToCheckedState(checked); 1082 } else { 1083 // Immediately move the thumb to the new position. 1084 cancelPositionAnimator(); 1085 setThumbPosition(checked ? 1 : 0); 1086 } 1087 } 1088 1089 @Override 1090 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1091 super.onLayout(changed, left, top, right, bottom); 1092 1093 int opticalInsetLeft = 0; 1094 int opticalInsetRight = 0; 1095 if (mThumbDrawable != null) { 1096 final Rect trackPadding = mTempRect; 1097 if (mTrackDrawable != null) { 1098 mTrackDrawable.getPadding(trackPadding); 1099 } else { 1100 trackPadding.setEmpty(); 1101 } 1102 1103 final Insets insets = mThumbDrawable.getOpticalInsets(); 1104 opticalInsetLeft = Math.max(0, insets.left - trackPadding.left); 1105 opticalInsetRight = Math.max(0, insets.right - trackPadding.right); 1106 } 1107 1108 final int switchRight; 1109 final int switchLeft; 1110 if (isLayoutRtl()) { 1111 switchLeft = getPaddingLeft() + opticalInsetLeft; 1112 switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight; 1113 } else { 1114 switchRight = getWidth() - getPaddingRight() - opticalInsetRight; 1115 switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight; 1116 } 1117 1118 final int switchTop; 1119 final int switchBottom; 1120 switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) { 1121 default: 1122 case Gravity.TOP: 1123 switchTop = getPaddingTop(); 1124 switchBottom = switchTop + mSwitchHeight; 1125 break; 1126 1127 case Gravity.CENTER_VERTICAL: 1128 switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 - 1129 mSwitchHeight / 2; 1130 switchBottom = switchTop + mSwitchHeight; 1131 break; 1132 1133 case Gravity.BOTTOM: 1134 switchBottom = getHeight() - getPaddingBottom(); 1135 switchTop = switchBottom - mSwitchHeight; 1136 break; 1137 } 1138 1139 mSwitchLeft = switchLeft; 1140 mSwitchTop = switchTop; 1141 mSwitchBottom = switchBottom; 1142 mSwitchRight = switchRight; 1143 } 1144 1145 @Override 1146 public void draw(Canvas c) { 1147 final Rect padding = mTempRect; 1148 final int switchLeft = mSwitchLeft; 1149 final int switchTop = mSwitchTop; 1150 final int switchRight = mSwitchRight; 1151 final int switchBottom = mSwitchBottom; 1152 1153 int thumbInitialLeft = switchLeft + getThumbOffset(); 1154 1155 final Insets thumbInsets; 1156 if (mThumbDrawable != null) { 1157 thumbInsets = mThumbDrawable.getOpticalInsets(); 1158 } else { 1159 thumbInsets = Insets.NONE; 1160 } 1161 1162 // Layout the track. 1163 if (mTrackDrawable != null) { 1164 mTrackDrawable.getPadding(padding); 1165 1166 // Adjust thumb position for track padding. 1167 thumbInitialLeft += padding.left; 1168 1169 // If necessary, offset by the optical insets of the thumb asset. 1170 int trackLeft = switchLeft; 1171 int trackTop = switchTop; 1172 int trackRight = switchRight; 1173 int trackBottom = switchBottom; 1174 if (thumbInsets != Insets.NONE) { 1175 if (thumbInsets.left > padding.left) { 1176 trackLeft += thumbInsets.left - padding.left; 1177 } 1178 if (thumbInsets.top > padding.top) { 1179 trackTop += thumbInsets.top - padding.top; 1180 } 1181 if (thumbInsets.right > padding.right) { 1182 trackRight -= thumbInsets.right - padding.right; 1183 } 1184 if (thumbInsets.bottom > padding.bottom) { 1185 trackBottom -= thumbInsets.bottom - padding.bottom; 1186 } 1187 } 1188 mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom); 1189 } 1190 1191 // Layout the thumb. 1192 if (mThumbDrawable != null) { 1193 mThumbDrawable.getPadding(padding); 1194 1195 final int thumbLeft = thumbInitialLeft - padding.left; 1196 final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right; 1197 mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom); 1198 1199 final Drawable background = getBackground(); 1200 if (background != null) { 1201 background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom); 1202 } 1203 } 1204 1205 // Draw the background. 1206 super.draw(c); 1207 } 1208 1209 @Override 1210 protected void onDraw(Canvas canvas) { 1211 super.onDraw(canvas); 1212 1213 final Rect padding = mTempRect; 1214 final Drawable trackDrawable = mTrackDrawable; 1215 if (trackDrawable != null) { 1216 trackDrawable.getPadding(padding); 1217 } else { 1218 padding.setEmpty(); 1219 } 1220 1221 final int switchTop = mSwitchTop; 1222 final int switchBottom = mSwitchBottom; 1223 final int switchInnerTop = switchTop + padding.top; 1224 final int switchInnerBottom = switchBottom - padding.bottom; 1225 1226 final Drawable thumbDrawable = mThumbDrawable; 1227 if (trackDrawable != null) { 1228 if (mSplitTrack && thumbDrawable != null) { 1229 final Insets insets = thumbDrawable.getOpticalInsets(); 1230 thumbDrawable.copyBounds(padding); 1231 padding.left += insets.left; 1232 padding.right -= insets.right; 1233 1234 final int saveCount = canvas.save(); 1235 canvas.clipRect(padding, Op.DIFFERENCE); 1236 trackDrawable.draw(canvas); 1237 canvas.restoreToCount(saveCount); 1238 } else { 1239 trackDrawable.draw(canvas); 1240 } 1241 } 1242 1243 final int saveCount = canvas.save(); 1244 1245 if (thumbDrawable != null) { 1246 thumbDrawable.draw(canvas); 1247 } 1248 1249 final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout; 1250 if (switchText != null) { 1251 final int drawableState[] = getDrawableState(); 1252 if (mTextColors != null) { 1253 mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0)); 1254 } 1255 mTextPaint.drawableState = drawableState; 1256 1257 final int cX; 1258 if (thumbDrawable != null) { 1259 final Rect bounds = thumbDrawable.getBounds(); 1260 cX = bounds.left + bounds.right; 1261 } else { 1262 cX = getWidth(); 1263 } 1264 1265 final int left = cX / 2 - switchText.getWidth() / 2; 1266 final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2; 1267 canvas.translate(left, top); 1268 switchText.draw(canvas); 1269 } 1270 1271 canvas.restoreToCount(saveCount); 1272 } 1273 1274 @Override 1275 public int getCompoundPaddingLeft() { 1276 if (!isLayoutRtl()) { 1277 return super.getCompoundPaddingLeft(); 1278 } 1279 int padding = super.getCompoundPaddingLeft() + mSwitchWidth; 1280 if (!TextUtils.isEmpty(getText())) { 1281 padding += mSwitchPadding; 1282 } 1283 return padding; 1284 } 1285 1286 @Override 1287 public int getCompoundPaddingRight() { 1288 if (isLayoutRtl()) { 1289 return super.getCompoundPaddingRight(); 1290 } 1291 int padding = super.getCompoundPaddingRight() + mSwitchWidth; 1292 if (!TextUtils.isEmpty(getText())) { 1293 padding += mSwitchPadding; 1294 } 1295 return padding; 1296 } 1297 1298 /** 1299 * Translates thumb position to offset according to current RTL setting and 1300 * thumb scroll range. Accounts for both track and thumb padding. 1301 * 1302 * @return thumb offset 1303 */ 1304 private int getThumbOffset() { 1305 final float thumbPosition; 1306 if (isLayoutRtl()) { 1307 thumbPosition = 1 - mThumbPosition; 1308 } else { 1309 thumbPosition = mThumbPosition; 1310 } 1311 return (int) (thumbPosition * getThumbScrollRange() + 0.5f); 1312 } 1313 1314 private int getThumbScrollRange() { 1315 if (mTrackDrawable != null) { 1316 final Rect padding = mTempRect; 1317 mTrackDrawable.getPadding(padding); 1318 1319 final Insets insets; 1320 if (mThumbDrawable != null) { 1321 insets = mThumbDrawable.getOpticalInsets(); 1322 } else { 1323 insets = Insets.NONE; 1324 } 1325 1326 return mSwitchWidth - mThumbWidth - padding.left - padding.right 1327 - insets.left - insets.right; 1328 } else { 1329 return 0; 1330 } 1331 } 1332 1333 @Override 1334 protected int[] onCreateDrawableState(int extraSpace) { 1335 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 1336 if (isChecked()) { 1337 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 1338 } 1339 return drawableState; 1340 } 1341 1342 @Override 1343 protected void drawableStateChanged() { 1344 super.drawableStateChanged(); 1345 1346 final int[] state = getDrawableState(); 1347 boolean changed = false; 1348 1349 final Drawable thumbDrawable = mThumbDrawable; 1350 if (thumbDrawable != null && thumbDrawable.isStateful()) { 1351 changed |= thumbDrawable.setState(state); 1352 } 1353 1354 final Drawable trackDrawable = mTrackDrawable; 1355 if (trackDrawable != null && trackDrawable.isStateful()) { 1356 changed |= trackDrawable.setState(state); 1357 } 1358 1359 if (changed) { 1360 invalidate(); 1361 } 1362 } 1363 1364 @Override 1365 public void drawableHotspotChanged(float x, float y) { 1366 super.drawableHotspotChanged(x, y); 1367 1368 if (mThumbDrawable != null) { 1369 mThumbDrawable.setHotspot(x, y); 1370 } 1371 1372 if (mTrackDrawable != null) { 1373 mTrackDrawable.setHotspot(x, y); 1374 } 1375 } 1376 1377 @Override 1378 protected boolean verifyDrawable(@NonNull Drawable who) { 1379 return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable; 1380 } 1381 1382 @Override 1383 public void jumpDrawablesToCurrentState() { 1384 super.jumpDrawablesToCurrentState(); 1385 1386 if (mThumbDrawable != null) { 1387 mThumbDrawable.jumpToCurrentState(); 1388 } 1389 1390 if (mTrackDrawable != null) { 1391 mTrackDrawable.jumpToCurrentState(); 1392 } 1393 1394 if (mPositionAnimator != null && mPositionAnimator.isStarted()) { 1395 mPositionAnimator.end(); 1396 mPositionAnimator = null; 1397 } 1398 } 1399 1400 @Override 1401 public CharSequence getAccessibilityClassName() { 1402 return Switch.class.getName(); 1403 } 1404 1405 @Override 1406 public void onProvideStructure(ViewStructure structure) { 1407 super.onProvideStructure(structure); 1408 CharSequence switchText = isChecked() ? mTextOn : mTextOff; 1409 if (!TextUtils.isEmpty(switchText)) { 1410 CharSequence oldText = structure.getText(); 1411 if (TextUtils.isEmpty(oldText)) { 1412 structure.setText(switchText); 1413 } else { 1414 StringBuilder newText = new StringBuilder(); 1415 newText.append(oldText).append(' ').append(switchText); 1416 structure.setText(newText); 1417 } 1418 // The style of the label text is provided via the base TextView class. This is more 1419 // relevant than the style of the (optional) on/off text on the switch button itself, 1420 // so ignore the size/color/style stored this.mTextPaint. 1421 } 1422 } 1423 1424 /** @hide */ 1425 @Override 1426 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1427 super.onInitializeAccessibilityNodeInfoInternal(info); 1428 CharSequence switchText = isChecked() ? mTextOn : mTextOff; 1429 if (!TextUtils.isEmpty(switchText)) { 1430 CharSequence oldText = info.getText(); 1431 if (TextUtils.isEmpty(oldText)) { 1432 info.setText(switchText); 1433 } else { 1434 StringBuilder newText = new StringBuilder(); 1435 newText.append(oldText).append(' ').append(switchText); 1436 info.setText(newText); 1437 } 1438 } 1439 } 1440 1441 private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") { 1442 @Override 1443 public Float get(Switch object) { 1444 return object.mThumbPosition; 1445 } 1446 1447 @Override 1448 public void setValue(Switch object, float value) { 1449 object.setThumbPosition(value); 1450 } 1451 }; 1452 } 1453