2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.widget;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.content.res.ColorStateList;
24 import android.content.res.TypedArray;
25 import android.graphics.BlendMode;
26 import android.graphics.Canvas;
27 import android.graphics.Insets;
28 import android.graphics.PorterDuff;
29 import android.graphics.Rect;
30 import android.graphics.Region.Op;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.util.AttributeSet;
34 import android.view.KeyEvent;
35 import android.view.MotionEvent;
36 import android.view.ViewConfiguration;
37 import android.view.accessibility.AccessibilityNodeInfo;
38 import android.view.inspector.InspectableProperty;
40 import com.android.internal.R;
41 import com.android.internal.util.Preconditions;
43 import java.util.ArrayList;
44 import java.util.Collections;
45 import java.util.List;
49 * AbsSeekBar extends the capabilities of ProgressBar by adding a draggable thumb.
51 public abstract class AbsSeekBar extends ProgressBar {
52 private final Rect mTempRect = new Rect();
55 private Drawable mThumb;
56 private ColorStateList mThumbTintList = null;
57 private BlendMode mThumbBlendMode = null;
58 private boolean mHasThumbTint = false;
59 private boolean mHasThumbBlendMode = false;
61 private Drawable mTickMark;
62 private ColorStateList mTickMarkTintList = null;
63 private BlendMode mTickMarkBlendMode = null;
64 private boolean mHasTickMarkTint = false;
65 private boolean mHasTickMarkBlendMode = false;
67 private int mThumbOffset;
69 private boolean mSplitTrack;
72 * On touch, this offset plus the scaled value from the position of the
73 * touch will form the progress value. Usually 0.
76 float mTouchProgressOffset;
79 * Whether this is user seekable.
82 boolean mIsUserSeekable = true;
85 * On key presses (right or left), the amount to increment/decrement the
88 private int mKeyProgressIncrement = 1;
90 private static final int NO_ALPHA = 0xFF;
92 private float mDisabledAlpha;
94 private int mThumbExclusionMaxSize;
95 private int mScaledTouchSlop;
96 private float mTouchDownX;
98 private boolean mIsDragging;
100 private List<Rect> mUserGestureExclusionRects = Collections.emptyList();
101 private final List<Rect> mGestureExclusionRects = new ArrayList<>();
102 private final Rect mThumbRect = new Rect();
104 public AbsSeekBar(Context context) {
108 public AbsSeekBar(Context context, AttributeSet attrs) {
109 super(context, attrs);
112 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
113 this(context, attrs, defStyleAttr, 0);
116 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
117 super(context, attrs, defStyleAttr, defStyleRes);
119 final TypedArray a = context.obtainStyledAttributes(
120 attrs, R.styleable.SeekBar, defStyleAttr, defStyleRes);
121 saveAttributeDataForStyleable(context, R.styleable.SeekBar, attrs, a, defStyleAttr,
124 final Drawable thumb = a.getDrawable(R.styleable.SeekBar_thumb);
127 if (a.hasValue(R.styleable.SeekBar_thumbTintMode)) {
128 mThumbBlendMode = Drawable.parseBlendMode(a.getInt(
129 R.styleable.SeekBar_thumbTintMode, -1), mThumbBlendMode);
130 mHasThumbBlendMode = true;
133 if (a.hasValue(R.styleable.SeekBar_thumbTint)) {
134 mThumbTintList = a.getColorStateList(R.styleable.SeekBar_thumbTint);
135 mHasThumbTint = true;
138 final Drawable tickMark = a.getDrawable(R.styleable.SeekBar_tickMark);
139 setTickMark(tickMark);
141 if (a.hasValue(R.styleable.SeekBar_tickMarkTintMode)) {
142 mTickMarkBlendMode = Drawable.parseBlendMode(a.getInt(
143 R.styleable.SeekBar_tickMarkTintMode, -1), mTickMarkBlendMode);
144 mHasTickMarkBlendMode = true;
147 if (a.hasValue(R.styleable.SeekBar_tickMarkTint)) {
148 mTickMarkTintList = a.getColorStateList(R.styleable.SeekBar_tickMarkTint);
149 mHasTickMarkTint = true;
152 mSplitTrack = a.getBoolean(R.styleable.SeekBar_splitTrack, false);
154 // Guess thumb offset if thumb != null, but allow layout to override.
155 final int thumbOffset = a.getDimensionPixelOffset(
156 R.styleable.SeekBar_thumbOffset, getThumbOffset());
157 setThumbOffset(thumbOffset);
159 final boolean useDisabledAlpha = a.getBoolean(R.styleable.SeekBar_useDisabledAlpha, true);
162 if (useDisabledAlpha) {
163 final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.Theme, 0, 0);
164 mDisabledAlpha = ta.getFloat(R.styleable.Theme_disabledAlpha, 0.5f);
167 mDisabledAlpha = 1.0f;
173 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
174 mThumbExclusionMaxSize = getResources().getDimensionPixelSize(
175 com.android.internal.R.dimen.seekbar_thumb_exclusion_max_size);
179 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar.
181 * If the thumb is a valid drawable (i.e. not null), half its width will be
182 * used as the new thumb offset (@see #setThumbOffset(int)).
184 * @param thumb Drawable representing the thumb
186 public void setThumb(Drawable thumb) {
187 final boolean needUpdate;
188 // This way, calling setThumb again with the same bitmap will result in
189 // it recalcuating mThumbOffset (if for example it the bounds of the
191 if (mThumb != null && thumb != mThumb) {
192 mThumb.setCallback(null);
199 thumb.setCallback(this);
200 if (canResolveLayoutDirection()) {
201 thumb.setLayoutDirection(getLayoutDirection());
204 // Assuming the thumb drawable is symmetric, set the thumb offset
205 // such that the thumb will hang halfway off either edge of the
207 mThumbOffset = thumb.getIntrinsicWidth() / 2;
209 // If we're updating get the new states
211 (thumb.getIntrinsicWidth() != mThumb.getIntrinsicWidth()
212 || thumb.getIntrinsicHeight() != mThumb.getIntrinsicHeight())) {
223 updateThumbAndTrackPos(getWidth(), getHeight());
224 if (thumb != null && thumb.isStateful()) {
225 // Note that if the states are different this won't work.
226 // For now, let's consider that an app bug.
227 int[] state = getDrawableState();
228 thumb.setState(state);
234 * Return the drawable used to represent the scroll thumb - the component that
235 * the user can drag back and forth indicating the current value by its position.
237 * @return The current thumb drawable
239 public Drawable getThumb() {
244 * Applies a tint to the thumb drawable. Does not modify the current tint
245 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
247 * Subsequent calls to {@link #setThumb(Drawable)} will automatically
248 * mutate the drawable and apply the specified tint and tint mode using
249 * {@link Drawable#setTintList(ColorStateList)}.
251 * @param tint the tint to apply, may be {@code null} to clear tint
253 * @attr ref android.R.styleable#SeekBar_thumbTint
254 * @see #getThumbTintList()
255 * @see Drawable#setTintList(ColorStateList)
257 public void setThumbTintList(@Nullable ColorStateList tint) {
258 mThumbTintList = tint;
259 mHasThumbTint = true;
265 * Returns the tint applied to the thumb drawable, if specified.
267 * @return the tint applied to the thumb drawable
268 * @attr ref android.R.styleable#SeekBar_thumbTint
269 * @see #setThumbTintList(ColorStateList)
271 @InspectableProperty(name = "thumbTint")
273 public ColorStateList getThumbTintList() {
274 return mThumbTintList;
278 * Specifies the blending mode used to apply the tint specified by
279 * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. The
280 * default mode is {@link PorterDuff.Mode#SRC_IN}.
282 * @param tintMode the blending mode used to apply the tint, may be
283 * {@code null} to clear tint
285 * @attr ref android.R.styleable#SeekBar_thumbTintMode
286 * @see #getThumbTintMode()
287 * @see Drawable#setTintMode(PorterDuff.Mode)
289 public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
290 setThumbTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) :
295 * Specifies the blending mode used to apply the tint specified by
296 * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. The
297 * default mode is {@link BlendMode#SRC_IN}.
299 * @param blendMode the blending mode used to apply the tint, may be
300 * {@code null} to clear tint
302 * @attr ref android.R.styleable#SeekBar_thumbTintMode
303 * @see #getThumbTintMode()
304 * @see Drawable#setTintBlendMode(BlendMode)
306 public void setThumbTintBlendMode(@Nullable BlendMode blendMode) {
307 mThumbBlendMode = blendMode;
308 mHasThumbBlendMode = true;
313 * Returns the blending mode used to apply the tint to the thumb drawable,
316 * @return the blending mode used to apply the tint to the thumb drawable
317 * @attr ref android.R.styleable#SeekBar_thumbTintMode
318 * @see #setThumbTintMode(PorterDuff.Mode)
322 public PorterDuff.Mode getThumbTintMode() {
323 return mThumbBlendMode != null
324 ? BlendMode.blendModeToPorterDuffMode(mThumbBlendMode) : null;
328 * Returns the blending mode used to apply the tint to the thumb drawable,
331 * @return the blending mode used to apply the tint to the thumb drawable
332 * @attr ref android.R.styleable#SeekBar_thumbTintMode
333 * @see #setThumbTintBlendMode(BlendMode)
336 public BlendMode getThumbTintBlendMode() {
337 return mThumbBlendMode;
340 private void applyThumbTint() {
341 if (mThumb != null && (mHasThumbTint || mHasThumbBlendMode)) {
342 mThumb = mThumb.mutate();
345 mThumb.setTintList(mThumbTintList);
348 if (mHasThumbBlendMode) {
349 mThumb.setTintBlendMode(mThumbBlendMode);
352 // The drawable (or one of its children) may not have been
353 // stateful before applying the tint, so let's try again.
354 if (mThumb.isStateful()) {
355 mThumb.setState(getDrawableState());
361 * @see #setThumbOffset(int)
363 public int getThumbOffset() {
368 * Sets the thumb offset that allows the thumb to extend out of the range of
371 * @param thumbOffset The offset amount in pixels.
373 public void setThumbOffset(int thumbOffset) {
374 mThumbOffset = thumbOffset;
379 * Specifies whether the track should be split by the thumb. When true,
380 * the thumb's optical bounds will be clipped out of the track drawable,
381 * then the thumb will be drawn into the resulting gap.
383 * @param splitTrack Whether the track should be split by the thumb
385 public void setSplitTrack(boolean splitTrack) {
386 mSplitTrack = splitTrack;
391 * Returns whether the track should be split by the thumb.
393 public boolean getSplitTrack() {
398 * Sets the drawable displayed at each progress position, e.g. at each
399 * possible thumb position.
401 * @param tickMark the drawable to display at each progress position
403 public void setTickMark(Drawable tickMark) {
404 if (mTickMark != null) {
405 mTickMark.setCallback(null);
408 mTickMark = tickMark;
410 if (tickMark != null) {
411 tickMark.setCallback(this);
412 tickMark.setLayoutDirection(getLayoutDirection());
413 if (tickMark.isStateful()) {
414 tickMark.setState(getDrawableState());
423 * @return the drawable displayed at each progress position
425 public Drawable getTickMark() {
430 * Applies a tint to the tick mark drawable. Does not modify the current tint
431 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
433 * Subsequent calls to {@link #setTickMark(Drawable)} will automatically
434 * mutate the drawable and apply the specified tint and tint mode using
435 * {@link Drawable#setTintList(ColorStateList)}.
437 * @param tint the tint to apply, may be {@code null} to clear tint
439 * @attr ref android.R.styleable#SeekBar_tickMarkTint
440 * @see #getTickMarkTintList()
441 * @see Drawable#setTintList(ColorStateList)
443 public void setTickMarkTintList(@Nullable ColorStateList tint) {
444 mTickMarkTintList = tint;
445 mHasTickMarkTint = true;
451 * Returns the tint applied to the tick mark drawable, if specified.
453 * @return the tint applied to the tick mark drawable
454 * @attr ref android.R.styleable#SeekBar_tickMarkTint
455 * @see #setTickMarkTintList(ColorStateList)
457 @InspectableProperty(name = "tickMarkTint")
459 public ColorStateList getTickMarkTintList() {
460 return mTickMarkTintList;
464 * Specifies the blending mode used to apply the tint specified by
465 * {@link #setTickMarkTintList(ColorStateList)}} to the tick mark drawable. The
466 * default mode is {@link PorterDuff.Mode#SRC_IN}.
468 * @param tintMode the blending mode used to apply the tint, may be
469 * {@code null} to clear tint
471 * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
472 * @see #getTickMarkTintMode()
473 * @see Drawable#setTintMode(PorterDuff.Mode)
475 public void setTickMarkTintMode(@Nullable PorterDuff.Mode tintMode) {
476 setTickMarkTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
480 * Specifies the blending mode used to apply the tint specified by
481 * {@link #setTickMarkTintList(ColorStateList)}} to the tick mark drawable. The
482 * default mode is {@link BlendMode#SRC_IN}.
484 * @param blendMode the blending mode used to apply the tint, may be
485 * {@code null} to clear tint
487 * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
488 * @see #getTickMarkTintMode()
489 * @see Drawable#setTintBlendMode(BlendMode)
491 public void setTickMarkTintBlendMode(@Nullable BlendMode blendMode) {
492 mTickMarkBlendMode = blendMode;
493 mHasTickMarkBlendMode = true;
499 * Returns the blending mode used to apply the tint to the tick mark drawable,
502 * @return the blending mode used to apply the tint to the tick mark drawable
503 * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
504 * @see #setTickMarkTintMode(PorterDuff.Mode)
508 public PorterDuff.Mode getTickMarkTintMode() {
509 return mTickMarkBlendMode != null
510 ? BlendMode.blendModeToPorterDuffMode(mTickMarkBlendMode) : null;
514 * Returns the blending mode used to apply the tint to the tick mark drawable,
517 * @return the blending mode used to apply the tint to the tick mark drawable
518 * @attr ref android.R.styleable#SeekBar_tickMarkTintMode
519 * @see #setTickMarkTintMode(PorterDuff.Mode)
521 @InspectableProperty(attributeId = android.R.styleable.SeekBar_tickMarkTintMode)
523 public BlendMode getTickMarkTintBlendMode() {
524 return mTickMarkBlendMode;
527 private void applyTickMarkTint() {
528 if (mTickMark != null && (mHasTickMarkTint || mHasTickMarkBlendMode)) {
529 mTickMark = mTickMark.mutate();
531 if (mHasTickMarkTint) {
532 mTickMark.setTintList(mTickMarkTintList);
535 if (mHasTickMarkBlendMode) {
536 mTickMark.setTintBlendMode(mTickMarkBlendMode);
539 // The drawable (or one of its children) may not have been
540 // stateful before applying the tint, so let's try again.
541 if (mTickMark.isStateful()) {
542 mTickMark.setState(getDrawableState());
548 * Sets the amount of progress changed via the arrow keys.
550 * @param increment The amount to increment or decrement when the user
551 * presses the arrow keys.
553 public void setKeyProgressIncrement(int increment) {
554 mKeyProgressIncrement = increment < 0 ? -increment : increment;
558 * Returns the amount of progress changed via the arrow keys.
560 * By default, this will be a value that is derived from the progress range.
562 * @return The amount to increment or decrement when the user presses the
563 * arrow keys. This will be positive.
565 public int getKeyProgressIncrement() {
566 return mKeyProgressIncrement;
570 public synchronized void setMin(int min) {
572 int range = getMax() - getMin();
574 if ((mKeyProgressIncrement == 0) || (range / mKeyProgressIncrement > 20)) {
576 // It will take the user too long to change this via keys, change it
577 // to something more reasonable
578 setKeyProgressIncrement(Math.max(1, Math.round((float) range / 20)));
583 public synchronized void setMax(int max) {
585 int range = getMax() - getMin();
587 if ((mKeyProgressIncrement == 0) || (range / mKeyProgressIncrement > 20)) {
588 // It will take the user too long to change this via keys, change it
589 // to something more reasonable
590 setKeyProgressIncrement(Math.max(1, Math.round((float) range / 20)));
595 protected boolean verifyDrawable(@NonNull Drawable who) {
596 return who == mThumb || who == mTickMark || super.verifyDrawable(who);
600 public void jumpDrawablesToCurrentState() {
601 super.jumpDrawablesToCurrentState();
603 if (mThumb != null) {
604 mThumb.jumpToCurrentState();
607 if (mTickMark != null) {
608 mTickMark.jumpToCurrentState();
613 protected void drawableStateChanged() {
614 super.drawableStateChanged();
616 final Drawable progressDrawable = getProgressDrawable();
617 if (progressDrawable != null && mDisabledAlpha < 1.0f) {
618 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
621 final Drawable thumb = mThumb;
622 if (thumb != null && thumb.isStateful()
623 && thumb.setState(getDrawableState())) {
624 invalidateDrawable(thumb);
627 final Drawable tickMark = mTickMark;
628 if (tickMark != null && tickMark.isStateful()
629 && tickMark.setState(getDrawableState())) {
630 invalidateDrawable(tickMark);
635 public void drawableHotspotChanged(float x, float y) {
636 super.drawableHotspotChanged(x, y);
638 if (mThumb != null) {
639 mThumb.setHotspot(x, y);
644 void onVisualProgressChanged(int id, float scale) {
645 super.onVisualProgressChanged(id, scale);
647 if (id == R.id.progress) {
648 final Drawable thumb = mThumb;
650 setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
652 // Since we draw translated, the drawable's bounds that it signals
653 // for invalidation won't be the actual bounds we want invalidated,
654 // so just invalidate this whole view.
661 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
662 super.onSizeChanged(w, h, oldw, oldh);
664 updateThumbAndTrackPos(w, h);
667 private void updateThumbAndTrackPos(int w, int h) {
668 final int paddedHeight = h - mPaddingTop - mPaddingBottom;
669 final Drawable track = getCurrentDrawable();
670 final Drawable thumb = mThumb;
672 // The max height does not incorporate padding, whereas the height
674 final int trackHeight = Math.min(mMaxHeight, paddedHeight);
675 final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
677 // Apply offset to whichever item is taller.
678 final int trackOffset;
679 final int thumbOffset;
680 if (thumbHeight > trackHeight) {
681 final int offsetHeight = (paddedHeight - thumbHeight) / 2;
682 trackOffset = offsetHeight + (thumbHeight - trackHeight) / 2;
683 thumbOffset = offsetHeight;
685 final int offsetHeight = (paddedHeight - trackHeight) / 2;
686 trackOffset = offsetHeight;
687 thumbOffset = offsetHeight + (trackHeight - thumbHeight) / 2;
691 final int trackWidth = w - mPaddingRight - mPaddingLeft;
692 track.setBounds(0, trackOffset, trackWidth, trackOffset + trackHeight);
696 setThumbPos(w, thumb, getScale(), thumbOffset);
700 private float getScale() {
703 int range = max - min;
704 return range > 0 ? (getProgress() - min) / (float) range : 0;
708 * Updates the thumb drawable bounds.
710 * @param w Width of the view, including padding
711 * @param thumb Drawable used for the thumb
712 * @param scale Current progress between 0 and 1
713 * @param offset Vertical offset for centering. If set to
714 * {@link Integer#MIN_VALUE}, the current offset will be used.
716 private void setThumbPos(int w, Drawable thumb, float scale, int offset) {
717 int available = w - mPaddingLeft - mPaddingRight;
718 final int thumbWidth = thumb.getIntrinsicWidth();
719 final int thumbHeight = thumb.getIntrinsicHeight();
720 available -= thumbWidth;
722 // The extra space for the thumb to move on the track
723 available += mThumbOffset * 2;
725 final int thumbPos = (int) (scale * available + 0.5f);
727 final int top, bottom;
728 if (offset == Integer.MIN_VALUE) {
729 final Rect oldBounds = thumb.getBounds();
731 bottom = oldBounds.bottom;
734 bottom = offset + thumbHeight;
737 final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
738 final int right = left + thumbWidth;
740 final Drawable background = getBackground();
741 if (background != null) {
742 final int offsetX = mPaddingLeft - mThumbOffset;
743 final int offsetY = mPaddingTop;
744 background.setHotspotBounds(left + offsetX, top + offsetY,
745 right + offsetX, bottom + offsetY);
748 // Canvas will be translated, so 0,0 is where we start drawing
749 thumb.setBounds(left, top, right, bottom);
750 updateGestureExclusionRects();
754 public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) {
755 Preconditions.checkNotNull(rects, "rects must not be null");
756 mUserGestureExclusionRects = rects;
757 updateGestureExclusionRects();
760 private void updateGestureExclusionRects() {
761 final Drawable thumb = mThumb;
763 super.setSystemGestureExclusionRects(mUserGestureExclusionRects);
766 mGestureExclusionRects.clear();
767 thumb.copyBounds(mThumbRect);
768 mThumbRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
769 growRectTo(mThumbRect, Math.min(getHeight(), mThumbExclusionMaxSize));
770 mGestureExclusionRects.add(mThumbRect);
771 mGestureExclusionRects.addAll(mUserGestureExclusionRects);
772 super.setSystemGestureExclusionRects(mGestureExclusionRects);
776 * Grows {@code r} from its center such that each dimension is at least {@code minimumSize}.
778 private void growRectTo(Rect r, int minimumSize) {
779 int dy = (minimumSize - r.height()) / 2;
784 int dx = (minimumSize - r.width()) / 2;
795 public void onResolveDrawables(int layoutDirection) {
796 super.onResolveDrawables(layoutDirection);
798 if (mThumb != null) {
799 mThumb.setLayoutDirection(layoutDirection);
804 protected synchronized void onDraw(Canvas canvas) {
805 super.onDraw(canvas);
810 void drawTrack(Canvas canvas) {
811 final Drawable thumbDrawable = mThumb;
812 if (thumbDrawable != null && mSplitTrack) {
813 final Insets insets = thumbDrawable.getOpticalInsets();
814 final Rect tempRect = mTempRect;
815 thumbDrawable.copyBounds(tempRect);
816 tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
817 tempRect.left += insets.left;
818 tempRect.right -= insets.right;
820 final int saveCount = canvas.save();
821 canvas.clipRect(tempRect, Op.DIFFERENCE);
822 super.drawTrack(canvas);
823 drawTickMarks(canvas);
824 canvas.restoreToCount(saveCount);
826 super.drawTrack(canvas);
827 drawTickMarks(canvas);
834 protected void drawTickMarks(Canvas canvas) {
835 if (mTickMark != null) {
836 final int count = getMax() - getMin();
838 final int w = mTickMark.getIntrinsicWidth();
839 final int h = mTickMark.getIntrinsicHeight();
840 final int halfW = w >= 0 ? w / 2 : 1;
841 final int halfH = h >= 0 ? h / 2 : 1;
842 mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
844 final float spacing = (getWidth() - mPaddingLeft - mPaddingRight) / (float) count;
845 final int saveCount = canvas.save();
846 canvas.translate(mPaddingLeft, getHeight() / 2);
847 for (int i = 0; i <= count; i++) {
848 mTickMark.draw(canvas);
849 canvas.translate(spacing, 0);
851 canvas.restoreToCount(saveCount);
860 void drawThumb(Canvas canvas) {
861 if (mThumb != null) {
862 final int saveCount = canvas.save();
863 // Translate the padding. For the x, we need to allow the thumb to
864 // draw in its extra space
865 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
867 canvas.restoreToCount(saveCount);
872 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
873 Drawable d = getCurrentDrawable();
875 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
879 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
880 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
881 dh = Math.max(thumbHeight, dh);
883 dw += mPaddingLeft + mPaddingRight;
884 dh += mPaddingTop + mPaddingBottom;
886 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
887 resolveSizeAndState(dh, heightMeasureSpec, 0));
891 public boolean onTouchEvent(MotionEvent event) {
892 if (!mIsUserSeekable || !isEnabled()) {
896 switch (event.getAction()) {
897 case MotionEvent.ACTION_DOWN:
898 if (isInScrollingContainer()) {
899 mTouchDownX = event.getX();
905 case MotionEvent.ACTION_MOVE:
907 trackTouchEvent(event);
909 final float x = event.getX();
910 if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
916 case MotionEvent.ACTION_UP:
918 trackTouchEvent(event);
919 onStopTrackingTouch();
922 // Touch up when we never crossed the touch slop threshold should
923 // be interpreted as a tap-seek to that location.
924 onStartTrackingTouch();
925 trackTouchEvent(event);
926 onStopTrackingTouch();
928 // ProgressBar doesn't know to repaint the thumb drawable
929 // in its inactive state when the touch stops (because the
930 // value has not apparently changed)
934 case MotionEvent.ACTION_CANCEL:
936 onStopTrackingTouch();
939 invalidate(); // see above explanation
945 private void startDrag(MotionEvent event) {
948 if (mThumb != null) {
949 // This may be within the padding region.
950 invalidate(mThumb.getBounds());
953 onStartTrackingTouch();
954 trackTouchEvent(event);
958 private void setHotspot(float x, float y) {
959 final Drawable bg = getBackground();
966 private void trackTouchEvent(MotionEvent event) {
967 final int x = Math.round(event.getX());
968 final int y = Math.round(event.getY());
969 final int width = getWidth();
970 final int availableWidth = width - mPaddingLeft - mPaddingRight;
973 float progress = 0.0f;
974 if (isLayoutRtl() && mMirrorForRtl) {
975 if (x > width - mPaddingRight) {
977 } else if (x < mPaddingLeft) {
980 scale = (availableWidth - x + mPaddingLeft) / (float) availableWidth;
981 progress = mTouchProgressOffset;
984 if (x < mPaddingLeft) {
986 } else if (x > width - mPaddingRight) {
989 scale = (x - mPaddingLeft) / (float) availableWidth;
990 progress = mTouchProgressOffset;
994 final int range = getMax() - getMin();
995 progress += scale * range + getMin();
998 setProgressInternal(Math.round(progress), true, false);
1002 * Tries to claim the user's drag motion, and requests disallowing any
1003 * ancestors from stealing events in the drag.
1005 private void attemptClaimDrag() {
1006 if (mParent != null) {
1007 mParent.requestDisallowInterceptTouchEvent(true);
1012 * This is called when the user has started touching this widget.
1014 void onStartTrackingTouch() {
1019 * This is called when the user either releases his touch or the touch is
1022 void onStopTrackingTouch() {
1023 mIsDragging = false;
1027 * Called when the user changes the seekbar's progress by using a key event.
1029 void onKeyChange() {
1033 public boolean onKeyDown(int keyCode, KeyEvent event) {
1035 int increment = mKeyProgressIncrement;
1037 case KeyEvent.KEYCODE_DPAD_LEFT:
1038 case KeyEvent.KEYCODE_MINUS:
1039 increment = -increment;
1041 case KeyEvent.KEYCODE_DPAD_RIGHT:
1042 case KeyEvent.KEYCODE_PLUS:
1043 case KeyEvent.KEYCODE_EQUALS:
1044 increment = isLayoutRtl() ? -increment : increment;
1046 if (setProgressInternal(getProgress() + increment, true, true)) {
1054 return super.onKeyDown(keyCode, event);
1058 public CharSequence getAccessibilityClassName() {
1059 return AbsSeekBar.class.getName();
1064 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1065 super.onInitializeAccessibilityNodeInfoInternal(info);
1068 final int progress = getProgress();
1069 if (progress > getMin()) {
1070 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
1072 if (progress < getMax()) {
1073 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
1080 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1081 if (super.performAccessibilityActionInternal(action, arguments)) {
1090 case R.id.accessibilityActionSetProgress: {
1091 if (!canUserSetProgress()) {
1094 if (arguments == null || !arguments.containsKey(
1095 AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)) {
1098 float value = arguments.getFloat(
1099 AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE);
1100 return setProgressInternal((int) value, true, true);
1102 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
1103 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
1104 if (!canUserSetProgress()) {
1107 int range = getMax() - getMin();
1108 int increment = Math.max(1, Math.round((float) range / 20));
1109 if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
1110 increment = -increment;
1113 // Let progress bar handle clamping values.
1114 if (setProgressInternal(getProgress() + increment, true, true)) {
1125 * @return whether user can change progress on the view
1127 boolean canUserSetProgress() {
1128 return !isIndeterminate() && isEnabled();
1132 public void onRtlPropertiesChanged(int layoutDirection) {
1133 super.onRtlPropertiesChanged(layoutDirection);
1135 final Drawable thumb = mThumb;
1136 if (thumb != null) {
1137 setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE);
1139 // Since we draw translated, the drawable's bounds that it signals
1140 // for invalidation won't be the actual bounds we want invalidated,
1141 // so just invalidate this whole view.