AbsSeekBar.java revision 4f64c048505a432e549ccb756634ecebf28f9e80
1/* 2 * Copyright (C) 2007 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.annotation.Nullable; 20import android.content.Context; 21import android.content.res.ColorStateList; 22import android.content.res.TypedArray; 23import android.graphics.Canvas; 24import android.graphics.Insets; 25import android.graphics.PorterDuff; 26import android.graphics.Rect; 27import android.graphics.Region.Op; 28import android.graphics.drawable.Drawable; 29import android.os.Bundle; 30import android.util.AttributeSet; 31import android.view.KeyEvent; 32import android.view.MotionEvent; 33import android.view.ViewConfiguration; 34import android.view.accessibility.AccessibilityEvent; 35import android.view.accessibility.AccessibilityNodeInfo; 36 37import com.android.internal.R; 38 39public abstract class AbsSeekBar extends ProgressBar { 40 private final Rect mTempRect = new Rect(); 41 42 private Drawable mThumb; 43 private ColorStateList mThumbTint = null; 44 private PorterDuff.Mode mThumbTintMode = PorterDuff.Mode.SRC_ATOP; 45 private boolean mHasThumbTint = false; 46 47 private int mThumbOffset; 48 private boolean mSplitTrack; 49 50 /** 51 * On touch, this offset plus the scaled value from the position of the 52 * touch will form the progress value. Usually 0. 53 */ 54 float mTouchProgressOffset; 55 56 /** 57 * Whether this is user seekable. 58 */ 59 boolean mIsUserSeekable = true; 60 61 /** 62 * On key presses (right or left), the amount to increment/decrement the 63 * progress. 64 */ 65 private int mKeyProgressIncrement = 1; 66 67 private static final int NO_ALPHA = 0xFF; 68 private float mDisabledAlpha; 69 70 private int mScaledTouchSlop; 71 private float mTouchDownX; 72 private boolean mIsDragging; 73 74 public AbsSeekBar(Context context) { 75 super(context); 76 } 77 78 public AbsSeekBar(Context context, AttributeSet attrs) { 79 super(context, attrs); 80 } 81 82 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { 83 this(context, attrs, defStyleAttr, 0); 84 } 85 86 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 87 super(context, attrs, defStyleAttr, defStyleRes); 88 89 TypedArray a = context.obtainStyledAttributes( 90 attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes); 91 92 final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); 93 setThumb(thumb); 94 95 mThumbTintMode = Drawable.parseTintMode(a.getInt( 96 R.styleable.SeekBar_thumbTintMode, -1), mThumbTintMode); 97 98 if (a.hasValue(R.styleable.SeekBar_thumbTint)) { 99 mThumbTint = a.getColorStateList(R.styleable.SeekBar_thumbTint); 100 mHasThumbTint = true; 101 102 applyThumbTint(); 103 } 104 105 // Guess thumb offset if thumb != null, but allow layout to override. 106 final int thumbOffset = a.getDimensionPixelOffset( 107 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset()); 108 setThumbOffset(thumbOffset); 109 110 mSplitTrack = a.getBoolean(com.android.internal.R.styleable.SeekBar_splitTrack, false); 111 a.recycle(); 112 113 a = context.obtainStyledAttributes(attrs, 114 com.android.internal.R.styleable.Theme, 0, 0); 115 mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f); 116 a.recycle(); 117 118 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 119 } 120 121 /** 122 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar. 123 * <p> 124 * If the thumb is a valid drawable (i.e. not null), half its width will be 125 * used as the new thumb offset (@see #setThumbOffset(int)). 126 * 127 * @param thumb Drawable representing the thumb 128 */ 129 public void setThumb(Drawable thumb) { 130 final boolean needUpdate; 131 // This way, calling setThumb again with the same bitmap will result in 132 // it recalcuating mThumbOffset (if for example it the bounds of the 133 // drawable changed) 134 if (mThumb != null && thumb != mThumb) { 135 mThumb.setCallback(null); 136 needUpdate = true; 137 } else { 138 needUpdate = false; 139 } 140 141 if (thumb != null) { 142 thumb.setCallback(this); 143 if (canResolveLayoutDirection()) { 144 thumb.setLayoutDirection(getLayoutDirection()); 145 } 146 147 // Assuming the thumb drawable is symmetric, set the thumb offset 148 // such that the thumb will hang halfway off either edge of the 149 // progress bar. 150 mThumbOffset = thumb.getIntrinsicWidth() / 2; 151 152 // If we're updating get the new states 153 if (needUpdate && 154 (thumb.getIntrinsicWidth() != mThumb.getIntrinsicWidth() 155 || thumb.getIntrinsicHeight() != mThumb.getIntrinsicHeight())) { 156 requestLayout(); 157 } 158 } 159 160 mThumb = thumb; 161 162 applyThumbTint(); 163 invalidate(); 164 165 if (needUpdate) { 166 updateThumbAndTrackPos(getWidth(), getHeight()); 167 if (thumb != null && thumb.isStateful()) { 168 // Note that if the states are different this won't work. 169 // For now, let's consider that an app bug. 170 int[] state = getDrawableState(); 171 thumb.setState(state); 172 } 173 } 174 } 175 176 /** 177 * Return the drawable used to represent the scroll thumb - the component that 178 * the user can drag back and forth indicating the current value by its position. 179 * 180 * @return The current thumb drawable 181 */ 182 public Drawable getThumb() { 183 return mThumb; 184 } 185 186 /** 187 * Applies a tint to the thumb drawable. Does not modify the current tint 188 * mode, which is {@link PorterDuff.Mode#SRC_ATOP} by default. 189 * <p> 190 * Subsequent calls to {@link #setThumb(Drawable)} will automatically 191 * mutate the drawable and apply the specified tint and tint mode using 192 * {@link Drawable#setTint(ColorStateList, PorterDuff.Mode)}. 193 * 194 * @param tint the tint to apply, may be {@code null} to clear tint 195 * 196 * @attr ref android.R.styleable#SeekBar_thumbTint 197 * @see #getThumbTint() 198 * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) 199 */ 200 public void setThumbTint(@Nullable ColorStateList tint) { 201 mThumbTint = tint; 202 mHasThumbTint = true; 203 204 applyThumbTint(); 205 } 206 207 /** 208 * @return the tint applied to the thumb drawable 209 * @attr ref android.R.styleable#SeekBar_thumbTint 210 * @see #setThumbTint(ColorStateList) 211 */ 212 @Nullable 213 public ColorStateList getThumbTint() { 214 return mThumbTint; 215 } 216 217 /** 218 * Specifies the blending mode used to apply the tint specified by 219 * {@link #setThumbTint(ColorStateList)}} to the thumb drawable. The 220 * default mode is {@link PorterDuff.Mode#SRC_ATOP}. 221 * 222 * @param tintMode the blending mode used to apply the tint, may be 223 * {@code null} to clear tint 224 * 225 * @attr ref android.R.styleable#SeekBar_thumbTintMode 226 * @see #getThumbTintMode() 227 * @see Drawable#setTint(ColorStateList, PorterDuff.Mode) 228 */ 229 public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) { 230 mThumbTintMode = tintMode; 231 232 applyThumbTint(); 233 } 234 235 /** 236 * @return the blending mode used to apply the tint to the thumb drawable 237 * @attr ref android.R.styleable#SeekBar_thumbTintMode 238 * @see #setThumbTintMode(PorterDuff.Mode) 239 */ 240 @Nullable 241 public PorterDuff.Mode getThumbTintMode() { 242 return mThumbTintMode; 243 } 244 245 private void applyThumbTint() { 246 if (mThumb != null && mHasThumbTint) { 247 mThumb = mThumb.mutate(); 248 mThumb.setTint(mThumbTint, mThumbTintMode); 249 } 250 } 251 252 /** 253 * @see #setThumbOffset(int) 254 */ 255 public int getThumbOffset() { 256 return mThumbOffset; 257 } 258 259 /** 260 * Sets the thumb offset that allows the thumb to extend out of the range of 261 * the track. 262 * 263 * @param thumbOffset The offset amount in pixels. 264 */ 265 public void setThumbOffset(int thumbOffset) { 266 mThumbOffset = thumbOffset; 267 invalidate(); 268 } 269 270 /** 271 * Specifies whether the track should be split by the thumb. When true, 272 * the thumb's optical bounds will be clipped out of the track drawable, 273 * then the thumb will be drawn into the resulting gap. 274 * 275 * @param splitTrack Whether the track should be split by the thumb 276 */ 277 public void setSplitTrack(boolean splitTrack) { 278 mSplitTrack = splitTrack; 279 invalidate(); 280 } 281 282 /** 283 * Returns whether the track should be split by the thumb. 284 */ 285 public boolean getSplitTrack() { 286 return mSplitTrack; 287 } 288 289 /** 290 * Sets the amount of progress changed via the arrow keys. 291 * 292 * @param increment The amount to increment or decrement when the user 293 * presses the arrow keys. 294 */ 295 public void setKeyProgressIncrement(int increment) { 296 mKeyProgressIncrement = increment < 0 ? -increment : increment; 297 } 298 299 /** 300 * Returns the amount of progress changed via the arrow keys. 301 * <p> 302 * By default, this will be a value that is derived from the max progress. 303 * 304 * @return The amount to increment or decrement when the user presses the 305 * arrow keys. This will be positive. 306 */ 307 public int getKeyProgressIncrement() { 308 return mKeyProgressIncrement; 309 } 310 311 @Override 312 public synchronized void setMax(int max) { 313 super.setMax(max); 314 315 if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) { 316 // It will take the user too long to change this via keys, change it 317 // to something more reasonable 318 setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20))); 319 } 320 } 321 322 @Override 323 protected boolean verifyDrawable(Drawable who) { 324 return who == mThumb || super.verifyDrawable(who); 325 } 326 327 @Override 328 public void jumpDrawablesToCurrentState() { 329 super.jumpDrawablesToCurrentState(); 330 331 if (mThumb != null) { 332 mThumb.jumpToCurrentState(); 333 } 334 } 335 336 @Override 337 protected void drawableStateChanged() { 338 super.drawableStateChanged(); 339 340 final Drawable progressDrawable = getProgressDrawable(); 341 if (progressDrawable != null) { 342 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha)); 343 } 344 345 final Drawable thumb = mThumb; 346 if (thumb != null && thumb.isStateful()) { 347 thumb.setState(getDrawableState()); 348 } 349 } 350 351 @Override 352 public void drawableHotspotChanged(float x, float y) { 353 super.drawableHotspotChanged(x, y); 354 355 if (mThumb != null) { 356 mThumb.setHotspot(x, y); 357 } 358 } 359 360 @Override 361 void onProgressRefresh(float scale, boolean fromUser) { 362 super.onProgressRefresh(scale, fromUser); 363 364 final Drawable thumb = mThumb; 365 if (thumb != null) { 366 setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); 367 368 // Since we draw translated, the drawable's bounds that it signals 369 // for invalidation won't be the actual bounds we want invalidated, 370 // so just invalidate this whole view. 371 invalidate(); 372 } 373 } 374 375 @Override 376 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 377 super.onSizeChanged(w, h, oldw, oldh); 378 379 updateThumbAndTrackPos(w, h); 380 } 381 382 private void updateThumbAndTrackPos(int w, int h) { 383 final Drawable track = getCurrentDrawable(); 384 final Drawable thumb = mThumb; 385 386 // The max height does not incorporate padding, whereas the height 387 // parameter does. 388 final int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom); 389 final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight(); 390 391 // Apply offset to whichever item is taller. 392 final int trackOffset; 393 final int thumbOffset; 394 if (thumbHeight > trackHeight) { 395 trackOffset = (thumbHeight - trackHeight) / 2; 396 thumbOffset = 0; 397 } else { 398 trackOffset = 0; 399 thumbOffset = (trackHeight - thumbHeight) / 2; 400 } 401 402 if (track != null) { 403 track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft, 404 h - mPaddingBottom - trackOffset - mPaddingTop); 405 } 406 407 if (thumb != null) { 408 setThumbPos(w, thumb, getScale(), thumbOffset); 409 } 410 } 411 412 private float getScale() { 413 final int max = getMax(); 414 return max > 0 ? getProgress() / (float) max : 0; 415 } 416 417 /** 418 * Updates the thumb drawable bounds. 419 * 420 * @param w Width of the view, including padding 421 * @param thumb Drawable used for the thumb 422 * @param scale Current progress between 0 and 1 423 * @param offset Vertical offset for centering. If set to 424 * {@link Integer#MIN_VALUE}, the current offset will be used. 425 */ 426 private void setThumbPos(int w, Drawable thumb, float scale, int offset) { 427 int available = w - mPaddingLeft - mPaddingRight; 428 final int thumbWidth = thumb.getIntrinsicWidth(); 429 final int thumbHeight = thumb.getIntrinsicHeight(); 430 available -= thumbWidth; 431 432 // The extra space for the thumb to move on the track 433 available += mThumbOffset * 2; 434 435 final int thumbPos = (int) (scale * available + 0.5f); 436 437 final int top, bottom; 438 if (offset == Integer.MIN_VALUE) { 439 final Rect oldBounds = thumb.getBounds(); 440 top = oldBounds.top; 441 bottom = oldBounds.bottom; 442 } else { 443 top = offset; 444 bottom = offset + thumbHeight; 445 } 446 447 final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos; 448 final int right = left + thumbWidth; 449 450 final Drawable background = getBackground(); 451 if (background != null) { 452 final Rect bounds = thumb.getBounds(); 453 final int offsetX = mPaddingLeft - mThumbOffset; 454 final int offsetY = mPaddingTop; 455 background.setHotspotBounds(left + offsetX, top + offsetY, 456 right + offsetX, bottom + offsetY); 457 } 458 459 // Canvas will be translated, so 0,0 is where we start drawing 460 thumb.setBounds(left, top, right, bottom); 461 } 462 463 /** 464 * @hide 465 */ 466 @Override 467 public void onResolveDrawables(int layoutDirection) { 468 super.onResolveDrawables(layoutDirection); 469 470 if (mThumb != null) { 471 mThumb.setLayoutDirection(layoutDirection); 472 } 473 } 474 475 @Override 476 protected synchronized void onDraw(Canvas canvas) { 477 super.onDraw(canvas); 478 drawThumb(canvas); 479 480 } 481 482 @Override 483 void drawTrack(Canvas canvas) { 484 final Drawable thumbDrawable = mThumb; 485 if (thumbDrawable != null && mSplitTrack) { 486 final Insets insets = thumbDrawable.getOpticalInsets(); 487 final Rect tempRect = mTempRect; 488 thumbDrawable.copyBounds(tempRect); 489 tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop); 490 tempRect.left += insets.left; 491 tempRect.right -= insets.right; 492 493 final int saveCount = canvas.save(); 494 canvas.clipRect(tempRect, Op.DIFFERENCE); 495 super.drawTrack(canvas); 496 canvas.restoreToCount(saveCount); 497 } else { 498 super.drawTrack(canvas); 499 } 500 } 501 502 /** 503 * Draw the thumb. 504 */ 505 void drawThumb(Canvas canvas) { 506 if (mThumb != null) { 507 canvas.save(); 508 // Translate the padding. For the x, we need to allow the thumb to 509 // draw in its extra space 510 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop); 511 mThumb.draw(canvas); 512 canvas.restore(); 513 } 514 } 515 516 @Override 517 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 518 Drawable d = getCurrentDrawable(); 519 520 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight(); 521 int dw = 0; 522 int dh = 0; 523 if (d != null) { 524 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 525 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 526 dh = Math.max(thumbHeight, dh); 527 } 528 dw += mPaddingLeft + mPaddingRight; 529 dh += mPaddingTop + mPaddingBottom; 530 531 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), 532 resolveSizeAndState(dh, heightMeasureSpec, 0)); 533 } 534 535 @Override 536 public boolean onTouchEvent(MotionEvent event) { 537 if (!mIsUserSeekable || !isEnabled()) { 538 return false; 539 } 540 541 switch (event.getAction()) { 542 case MotionEvent.ACTION_DOWN: 543 if (isInScrollingContainer()) { 544 mTouchDownX = event.getX(); 545 } else { 546 setPressed(true); 547 if (mThumb != null) { 548 invalidate(mThumb.getBounds()); // This may be within the padding region 549 } 550 onStartTrackingTouch(); 551 trackTouchEvent(event); 552 attemptClaimDrag(); 553 } 554 break; 555 556 case MotionEvent.ACTION_MOVE: 557 if (mIsDragging) { 558 trackTouchEvent(event); 559 } else { 560 final float x = event.getX(); 561 if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) { 562 setPressed(true); 563 if (mThumb != null) { 564 invalidate(mThumb.getBounds()); // This may be within the padding region 565 } 566 onStartTrackingTouch(); 567 trackTouchEvent(event); 568 attemptClaimDrag(); 569 } 570 } 571 break; 572 573 case MotionEvent.ACTION_UP: 574 if (mIsDragging) { 575 trackTouchEvent(event); 576 onStopTrackingTouch(); 577 setPressed(false); 578 } else { 579 // Touch up when we never crossed the touch slop threshold should 580 // be interpreted as a tap-seek to that location. 581 onStartTrackingTouch(); 582 trackTouchEvent(event); 583 onStopTrackingTouch(); 584 } 585 // ProgressBar doesn't know to repaint the thumb drawable 586 // in its inactive state when the touch stops (because the 587 // value has not apparently changed) 588 invalidate(); 589 break; 590 591 case MotionEvent.ACTION_CANCEL: 592 if (mIsDragging) { 593 onStopTrackingTouch(); 594 setPressed(false); 595 } 596 invalidate(); // see above explanation 597 break; 598 } 599 return true; 600 } 601 602 private void setHotspot(float x, float y) { 603 final Drawable bg = getBackground(); 604 if (bg != null) { 605 bg.setHotspot(x, y); 606 } 607 } 608 609 private void trackTouchEvent(MotionEvent event) { 610 final int width = getWidth(); 611 final int available = width - mPaddingLeft - mPaddingRight; 612 final int x = (int) event.getX(); 613 float scale; 614 float progress = 0; 615 if (isLayoutRtl() && mMirrorForRtl) { 616 if (x > width - mPaddingRight) { 617 scale = 0.0f; 618 } else if (x < mPaddingLeft) { 619 scale = 1.0f; 620 } else { 621 scale = (float)(available - x + mPaddingLeft) / (float)available; 622 progress = mTouchProgressOffset; 623 } 624 } else { 625 if (x < mPaddingLeft) { 626 scale = 0.0f; 627 } else if (x > width - mPaddingRight) { 628 scale = 1.0f; 629 } else { 630 scale = (float)(x - mPaddingLeft) / (float)available; 631 progress = mTouchProgressOffset; 632 } 633 } 634 final int max = getMax(); 635 progress += scale * max; 636 637 setHotspot(x, (int) event.getY()); 638 setProgress((int) progress, true); 639 } 640 641 /** 642 * Tries to claim the user's drag motion, and requests disallowing any 643 * ancestors from stealing events in the drag. 644 */ 645 private void attemptClaimDrag() { 646 if (mParent != null) { 647 mParent.requestDisallowInterceptTouchEvent(true); 648 } 649 } 650 651 /** 652 * This is called when the user has started touching this widget. 653 */ 654 void onStartTrackingTouch() { 655 mIsDragging = true; 656 } 657 658 /** 659 * This is called when the user either releases his touch or the touch is 660 * canceled. 661 */ 662 void onStopTrackingTouch() { 663 mIsDragging = false; 664 } 665 666 /** 667 * Called when the user changes the seekbar's progress by using a key event. 668 */ 669 void onKeyChange() { 670 } 671 672 @Override 673 public boolean onKeyDown(int keyCode, KeyEvent event) { 674 if (isEnabled()) { 675 int progress = getProgress(); 676 switch (keyCode) { 677 case KeyEvent.KEYCODE_DPAD_LEFT: 678 if (progress <= 0) break; 679 setProgress(progress - mKeyProgressIncrement, true); 680 onKeyChange(); 681 return true; 682 683 case KeyEvent.KEYCODE_DPAD_RIGHT: 684 if (progress >= getMax()) break; 685 setProgress(progress + mKeyProgressIncrement, true); 686 onKeyChange(); 687 return true; 688 } 689 } 690 691 return super.onKeyDown(keyCode, event); 692 } 693 694 @Override 695 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 696 super.onInitializeAccessibilityEvent(event); 697 event.setClassName(AbsSeekBar.class.getName()); 698 } 699 700 @Override 701 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 702 super.onInitializeAccessibilityNodeInfo(info); 703 info.setClassName(AbsSeekBar.class.getName()); 704 705 if (isEnabled()) { 706 final int progress = getProgress(); 707 if (progress > 0) { 708 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 709 } 710 if (progress < getMax()) { 711 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 712 } 713 } 714 } 715 716 @Override 717 public boolean performAccessibilityAction(int action, Bundle arguments) { 718 if (super.performAccessibilityAction(action, arguments)) { 719 return true; 720 } 721 if (!isEnabled()) { 722 return false; 723 } 724 final int progress = getProgress(); 725 final int increment = Math.max(1, Math.round((float) getMax() / 5)); 726 switch (action) { 727 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 728 if (progress <= 0) { 729 return false; 730 } 731 setProgress(progress - increment, true); 732 onKeyChange(); 733 return true; 734 } 735 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 736 if (progress >= getMax()) { 737 return false; 738 } 739 setProgress(progress + increment, true); 740 onKeyChange(); 741 return true; 742 } 743 } 744 return false; 745 } 746 747 @Override 748 public void onRtlPropertiesChanged(int layoutDirection) { 749 super.onRtlPropertiesChanged(layoutDirection); 750 751 final Drawable thumb = mThumb; 752 if (thumb != null) { 753 setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE); 754 755 // Since we draw translated, the drawable's bounds that it signals 756 // for invalidation won't be the actual bounds we want invalidated, 757 // so just invalidate this whole view. 758 invalidate(); 759 } 760 } 761} 762