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