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