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