blob: d6f9f786c033cb9b0a86d584772d90ae07847217 [file] [log] [blame]
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -07001/*
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
Alan Viverette91174362014-06-17 14:51:45 -070019import android.annotation.Nullable;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070020import android.content.Context;
Alan Viverette91174362014-06-17 14:51:45 -070021import android.content.res.ColorStateList;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070022import android.content.res.TypedArray;
23import android.graphics.Canvas;
Alan Viverette661e6362014-05-12 10:55:37 -070024import android.graphics.Insets;
Alan Viverette91174362014-06-17 14:51:45 -070025import android.graphics.PorterDuff;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070026import android.graphics.Rect;
Alan Viverette661e6362014-05-12 10:55:37 -070027import android.graphics.Region.Op;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070028import android.graphics.drawable.Drawable;
alanvc826b7d2012-05-16 14:19:21 -070029import android.os.Bundle;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070030import android.util.AttributeSet;
The Android Open Source Projectb7986892009-01-09 17:51:23 -080031import android.view.KeyEvent;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070032import android.view.MotionEvent;
Adam Powell10298662011-08-14 18:26:30 -070033import android.view.ViewConfiguration;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -080034import android.view.accessibility.AccessibilityNodeInfo;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070035
Alan Viverette91174362014-06-17 14:51:45 -070036import com.android.internal.R;
37
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070038public abstract class AbsSeekBar extends ProgressBar {
Alan Viverette661e6362014-05-12 10:55:37 -070039 private final Rect mTempRect = new Rect();
40
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070041 private Drawable mThumb;
Alan Viverettea4264452014-07-28 16:02:55 -070042 private ColorStateList mThumbTintList = null;
Alan Viveretteb56f5d22014-09-14 15:48:50 -070043 private PorterDuff.Mode mThumbTintMode = null;
Alan Viverette91174362014-06-17 14:51:45 -070044 private boolean mHasThumbTint = false;
Alan Viveretteb56f5d22014-09-14 15:48:50 -070045 private boolean mHasThumbTintMode = false;
Alan Viverette91174362014-06-17 14:51:45 -070046
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070047 private int mThumbOffset;
Alan Viverette661e6362014-05-12 10:55:37 -070048 private boolean mSplitTrack;
49
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070050 /**
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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060
61 /**
62 * On key presses (right or left), the amount to increment/decrement the
63 * progress.
64 */
65 private int mKeyProgressIncrement = 1;
Alan Viverette661e6362014-05-12 10:55:37 -070066
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070067 private static final int NO_ALPHA = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068 private float mDisabledAlpha;
Alan Viverette661e6362014-05-12 10:55:37 -070069
Adam Powell10298662011-08-14 18:26:30 -070070 private int mScaledTouchSlop;
71 private float mTouchDownX;
72 private boolean mIsDragging;
73
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070074 public AbsSeekBar(Context context) {
75 super(context);
76 }
77
78 public AbsSeekBar(Context context, AttributeSet attrs) {
79 super(context, attrs);
80 }
81
Alan Viverette617feb92013-09-09 18:09:13 -070082 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
83 this(context, attrs, defStyleAttr, 0);
84 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070085
Alan Viverette617feb92013-09-09 18:09:13 -070086 public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
87 super(context, attrs, defStyleAttr, defStyleRes);
88
89 TypedArray a = context.obtainStyledAttributes(
90 attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes);
Alan Viverette661e6362014-05-12 10:55:37 -070091
92 final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
93 setThumb(thumb);
94
Alan Viveretteb56f5d22014-09-14 15:48:50 -070095 if (a.hasValue(R.styleable.SeekBar_thumbTintMode)) {
96 mThumbTintMode = Drawable.parseTintMode(a.getInt(
97 R.styleable.SeekBar_thumbTintMode, -1), mThumbTintMode);
98 mHasThumbTintMode = true;
99 }
Alan Viverette4f64c042014-07-21 17:49:13 -0700100
Alan Viverette91174362014-06-17 14:51:45 -0700101 if (a.hasValue(R.styleable.SeekBar_thumbTint)) {
Alan Viverettea4264452014-07-28 16:02:55 -0700102 mThumbTintList = a.getColorStateList(R.styleable.SeekBar_thumbTint);
Alan Viverette91174362014-06-17 14:51:45 -0700103 mHasThumbTint = true;
Alan Viverette91174362014-06-17 14:51:45 -0700104 }
105
Alan Viverette661e6362014-05-12 10:55:37 -0700106 // Guess thumb offset if thumb != null, but allow layout to override.
107 final int thumbOffset = a.getDimensionPixelOffset(
Romain Guy992cc422010-01-04 15:39:40 -0800108 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
109 setThumbOffset(thumbOffset);
Alan Viverette661e6362014-05-12 10:55:37 -0700110
111 mSplitTrack = a.getBoolean(com.android.internal.R.styleable.SeekBar_splitTrack, false);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700112 a.recycle();
113
114 a = context.obtainStyledAttributes(attrs,
115 com.android.internal.R.styleable.Theme, 0, 0);
116 mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
117 a.recycle();
Adam Powell10298662011-08-14 18:26:30 -0700118
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700119 applyThumbTint();
120
Adam Powell10298662011-08-14 18:26:30 -0700121 mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700122 }
123
124 /**
Daniel Sandler8d28c3b2009-08-20 13:34:02 -0400125 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar.
126 * <p>
127 * If the thumb is a valid drawable (i.e. not null), half its width will be
128 * used as the new thumb offset (@see #setThumbOffset(int)).
Alan Viverette661e6362014-05-12 10:55:37 -0700129 *
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700130 * @param thumb Drawable representing the thumb
131 */
132 public void setThumb(Drawable thumb) {
Alan Viverette91174362014-06-17 14:51:45 -0700133 final boolean needUpdate;
Joe Onorato2e585f72010-12-02 14:10:57 -0800134 // This way, calling setThumb again with the same bitmap will result in
135 // it recalcuating mThumbOffset (if for example it the bounds of the
136 // drawable changed)
137 if (mThumb != null && thumb != mThumb) {
138 mThumb.setCallback(null);
139 needUpdate = true;
140 } else {
141 needUpdate = false;
142 }
Alan Viverette91174362014-06-17 14:51:45 -0700143
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700144 if (thumb != null) {
145 thumb.setCallback(this);
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700146 if (canResolveLayoutDirection()) {
Fabrice Di Meglioe56ffdc2012-09-23 14:51:16 -0700147 thumb.setLayoutDirection(getLayoutDirection());
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700148 }
Daniel Sandler8d28c3b2009-08-20 13:34:02 -0400149
150 // Assuming the thumb drawable is symmetric, set the thumb offset
151 // such that the thumb will hang halfway off either edge of the
152 // progress bar.
Romain Guy992cc422010-01-04 15:39:40 -0800153 mThumbOffset = thumb.getIntrinsicWidth() / 2;
Joe Onorato2e585f72010-12-02 14:10:57 -0800154
155 // If we're updating get the new states
156 if (needUpdate &&
157 (thumb.getIntrinsicWidth() != mThumb.getIntrinsicWidth()
158 || thumb.getIntrinsicHeight() != mThumb.getIntrinsicHeight())) {
159 requestLayout();
160 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700161 }
Alan Viverette91174362014-06-17 14:51:45 -0700162
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700163 mThumb = thumb;
Alan Viverette91174362014-06-17 14:51:45 -0700164
165 applyThumbTint();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700166 invalidate();
Alan Viverette91174362014-06-17 14:51:45 -0700167
Joe Onorato2e585f72010-12-02 14:10:57 -0800168 if (needUpdate) {
Alan Viverette661e6362014-05-12 10:55:37 -0700169 updateThumbAndTrackPos(getWidth(), getHeight());
Adam Powell0283a552012-04-19 09:58:22 -0700170 if (thumb != null && thumb.isStateful()) {
Joe Onorato2e585f72010-12-02 14:10:57 -0800171 // Note that if the states are different this won't work.
172 // For now, let's consider that an app bug.
173 int[] state = getDrawableState();
174 thumb.setState(state);
175 }
176 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700177 }
178
179 /**
Adam Powell3004cc52012-03-21 17:50:51 -0700180 * Return the drawable used to represent the scroll thumb - the component that
181 * the user can drag back and forth indicating the current value by its position.
182 *
183 * @return The current thumb drawable
184 */
185 public Drawable getThumb() {
186 return mThumb;
187 }
188
189 /**
Alan Viverette91174362014-06-17 14:51:45 -0700190 * Applies a tint to the thumb drawable. Does not modify the current tint
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700191 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
Alan Viverette91174362014-06-17 14:51:45 -0700192 * <p>
193 * Subsequent calls to {@link #setThumb(Drawable)} will automatically
194 * mutate the drawable and apply the specified tint and tint mode using
Alan Viverettea4264452014-07-28 16:02:55 -0700195 * {@link Drawable#setTintList(ColorStateList)}.
Alan Viverette91174362014-06-17 14:51:45 -0700196 *
197 * @param tint the tint to apply, may be {@code null} to clear tint
198 *
199 * @attr ref android.R.styleable#SeekBar_thumbTint
Alan Viverettea4264452014-07-28 16:02:55 -0700200 * @see #getThumbTintList()
201 * @see Drawable#setTintList(ColorStateList)
Alan Viverette91174362014-06-17 14:51:45 -0700202 */
Alan Viverettea4264452014-07-28 16:02:55 -0700203 public void setThumbTintList(@Nullable ColorStateList tint) {
204 mThumbTintList = tint;
Alan Viverette4f64c042014-07-21 17:49:13 -0700205 mHasThumbTint = true;
206
207 applyThumbTint();
Alan Viverette91174362014-06-17 14:51:45 -0700208 }
209
210 /**
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700211 * Returns the tint applied to the thumb drawable, if specified.
212 *
Alan Viverette91174362014-06-17 14:51:45 -0700213 * @return the tint applied to the thumb drawable
214 * @attr ref android.R.styleable#SeekBar_thumbTint
Alan Viverettea4264452014-07-28 16:02:55 -0700215 * @see #setThumbTintList(ColorStateList)
Alan Viverette91174362014-06-17 14:51:45 -0700216 */
217 @Nullable
Alan Viverettea4264452014-07-28 16:02:55 -0700218 public ColorStateList getThumbTintList() {
219 return mThumbTintList;
Alan Viverette91174362014-06-17 14:51:45 -0700220 }
221
222 /**
223 * Specifies the blending mode used to apply the tint specified by
Alan Viverettea4264452014-07-28 16:02:55 -0700224 * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. The
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700225 * default mode is {@link PorterDuff.Mode#SRC_IN}.
Alan Viverette91174362014-06-17 14:51:45 -0700226 *
227 * @param tintMode the blending mode used to apply the tint, may be
228 * {@code null} to clear tint
Alan Viverette4f64c042014-07-21 17:49:13 -0700229 *
Alan Viverette91174362014-06-17 14:51:45 -0700230 * @attr ref android.R.styleable#SeekBar_thumbTintMode
Alan Viverette4f64c042014-07-21 17:49:13 -0700231 * @see #getThumbTintMode()
Alan Viverettea4264452014-07-28 16:02:55 -0700232 * @see Drawable#setTintMode(PorterDuff.Mode)
Alan Viverette91174362014-06-17 14:51:45 -0700233 */
234 public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
Alan Viverette4f64c042014-07-21 17:49:13 -0700235 mThumbTintMode = tintMode;
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700236 mHasThumbTintMode = true;
Alan Viverette4f64c042014-07-21 17:49:13 -0700237
238 applyThumbTint();
Alan Viverette91174362014-06-17 14:51:45 -0700239 }
240
241 /**
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700242 * Returns the blending mode used to apply the tint to the thumb drawable,
243 * if specified.
244 *
Alan Viverette91174362014-06-17 14:51:45 -0700245 * @return the blending mode used to apply the tint to the thumb drawable
246 * @attr ref android.R.styleable#SeekBar_thumbTintMode
Alan Viverette4f64c042014-07-21 17:49:13 -0700247 * @see #setThumbTintMode(PorterDuff.Mode)
Alan Viverette91174362014-06-17 14:51:45 -0700248 */
249 @Nullable
250 public PorterDuff.Mode getThumbTintMode() {
251 return mThumbTintMode;
252 }
253
254 private void applyThumbTint() {
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700255 if (mThumb != null && (mHasThumbTint || mHasThumbTintMode)) {
Alan Viverette91174362014-06-17 14:51:45 -0700256 mThumb = mThumb.mutate();
Alan Viveretteb56f5d22014-09-14 15:48:50 -0700257
258 if (mHasThumbTint) {
259 mThumb.setTintList(mThumbTintList);
260 }
261
262 if (mHasThumbTintMode) {
263 mThumb.setTintMode(mThumbTintMode);
264 }
Alan Viveretted5133792014-10-28 14:41:36 -0700265
266 // The drawable (or one of its children) may not have been
267 // stateful before applying the tint, so let's try again.
268 if (mThumb.isStateful()) {
269 mThumb.setState(getDrawableState());
270 }
Alan Viverette91174362014-06-17 14:51:45 -0700271 }
272 }
273
274 /**
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700275 * @see #setThumbOffset(int)
276 */
277 public int getThumbOffset() {
278 return mThumbOffset;
279 }
280
281 /**
282 * Sets the thumb offset that allows the thumb to extend out of the range of
283 * the track.
Alan Viverette661e6362014-05-12 10:55:37 -0700284 *
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700285 * @param thumbOffset The offset amount in pixels.
286 */
287 public void setThumbOffset(int thumbOffset) {
288 mThumbOffset = thumbOffset;
289 invalidate();
290 }
291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 /**
Alan Viverette661e6362014-05-12 10:55:37 -0700293 * Specifies whether the track should be split by the thumb. When true,
294 * the thumb's optical bounds will be clipped out of the track drawable,
295 * then the thumb will be drawn into the resulting gap.
296 *
297 * @param splitTrack Whether the track should be split by the thumb
298 */
299 public void setSplitTrack(boolean splitTrack) {
300 mSplitTrack = splitTrack;
301 invalidate();
302 }
303
304 /**
305 * Returns whether the track should be split by the thumb.
306 */
307 public boolean getSplitTrack() {
308 return mSplitTrack;
309 }
310
311 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800312 * Sets the amount of progress changed via the arrow keys.
Alan Viverette661e6362014-05-12 10:55:37 -0700313 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 * @param increment The amount to increment or decrement when the user
315 * presses the arrow keys.
316 */
317 public void setKeyProgressIncrement(int increment) {
318 mKeyProgressIncrement = increment < 0 ? -increment : increment;
319 }
320
321 /**
322 * Returns the amount of progress changed via the arrow keys.
323 * <p>
324 * By default, this will be a value that is derived from the max progress.
Alan Viverette661e6362014-05-12 10:55:37 -0700325 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 * @return The amount to increment or decrement when the user presses the
327 * arrow keys. This will be positive.
328 */
329 public int getKeyProgressIncrement() {
330 return mKeyProgressIncrement;
331 }
Alan Viverette661e6362014-05-12 10:55:37 -0700332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 @Override
334 public synchronized void setMax(int max) {
335 super.setMax(max);
336
337 if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
338 // It will take the user too long to change this via keys, change it
339 // to something more reasonable
340 setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
341 }
342 }
343
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700344 @Override
345 protected boolean verifyDrawable(Drawable who) {
346 return who == mThumb || super.verifyDrawable(who);
347 }
348
349 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -0700350 public void jumpDrawablesToCurrentState() {
351 super.jumpDrawablesToCurrentState();
Alan Viverette8de14942014-06-18 18:05:15 -0700352
353 if (mThumb != null) {
354 mThumb.jumpToCurrentState();
355 }
Dianne Hackborne2136772010-11-04 15:08:59 -0700356 }
357
358 @Override
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700359 protected void drawableStateChanged() {
360 super.drawableStateChanged();
Alan Viverette661e6362014-05-12 10:55:37 -0700361
362 final Drawable progressDrawable = getProgressDrawable();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700363 if (progressDrawable != null) {
364 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
365 }
Alan Viverette661e6362014-05-12 10:55:37 -0700366
367 final Drawable thumb = mThumb;
368 if (thumb != null && thumb.isStateful()) {
369 thumb.setState(getDrawableState());
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800370 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700371 }
Alan Viverette661e6362014-05-12 10:55:37 -0700372
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700373 @Override
Alan Viverette8de14942014-06-18 18:05:15 -0700374 public void drawableHotspotChanged(float x, float y) {
375 super.drawableHotspotChanged(x, y);
Alan Viverettecebc6ba2014-06-13 15:52:13 -0700376
Alan Viverette8de14942014-06-18 18:05:15 -0700377 if (mThumb != null) {
378 mThumb.setHotspot(x, y);
Alan Viverette2356c5e2014-05-22 22:43:59 -0700379 }
380 }
381
382 @Override
RoboErik5b071432015-02-11 13:52:05 -0800383 void onProgressRefresh(float scale, boolean fromUser, int progress) {
384 super.onProgressRefresh(scale, fromUser, progress);
Alan Viverette661e6362014-05-12 10:55:37 -0700385
Alan Viverette5ce0ec02014-11-25 09:40:54 -0800386 final Drawable thumb = mThumb;
387 if (thumb != null) {
388 setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
Alan Viverette661e6362014-05-12 10:55:37 -0700389
Alan Viverette5ce0ec02014-11-25 09:40:54 -0800390 // Since we draw translated, the drawable's bounds that it signals
391 // for invalidation won't be the actual bounds we want invalidated,
392 // so just invalidate this whole view.
393 invalidate();
394 }
Jon Miranda0c11a482014-07-21 14:55:29 -0700395 }
396
397 @Override
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700398 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Fabrice Di Megliof37df1b2012-10-15 19:10:08 -0700399 super.onSizeChanged(w, h, oldw, oldh);
Alan Viverette661e6362014-05-12 10:55:37 -0700400
401 updateThumbAndTrackPos(w, h);
Joe Onorato2e585f72010-12-02 14:10:57 -0800402 }
403
Alan Viverette661e6362014-05-12 10:55:37 -0700404 private void updateThumbAndTrackPos(int w, int h) {
Alan Viverette3afd8b02015-02-26 13:36:28 -0800405 final int paddedHeight = h - mPaddingTop - mPaddingBottom;
Alan Viverette661e6362014-05-12 10:55:37 -0700406 final Drawable track = getCurrentDrawable();
407 final Drawable thumb = mThumb;
408
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700409 // The max height does not incorporate padding, whereas the height
Alan Viverette661e6362014-05-12 10:55:37 -0700410 // parameter does.
Alan Viverette3afd8b02015-02-26 13:36:28 -0800411 final int trackHeight = Math.min(mMaxHeight, paddedHeight);
Alan Viverette661e6362014-05-12 10:55:37 -0700412 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;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700417 if (thumbHeight > trackHeight) {
Alan Viverette3afd8b02015-02-26 13:36:28 -0800418 final int offsetHeight = (paddedHeight - thumbHeight) / 2;
419 trackOffset = offsetHeight + (thumbHeight - trackHeight) / 2;
420 thumbOffset = offsetHeight + 0;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700421 } else {
Alan Viverette3afd8b02015-02-26 13:36:28 -0800422 final int offsetHeight = (paddedHeight - trackHeight) / 2;
423 trackOffset = offsetHeight + 0;
424 thumbOffset = offsetHeight + (trackHeight - thumbHeight) / 2;
Alan Viverette661e6362014-05-12 10:55:37 -0700425 }
426
427 if (track != null) {
Alan Viverette6a8253f2015-02-23 12:49:47 -0800428 final int trackWidth = w - mPaddingRight - mPaddingLeft;
429 track.setBounds(0, trackOffset, trackWidth, trackOffset + trackHeight);
Alan Viverette661e6362014-05-12 10:55:37 -0700430 }
431
432 if (thumb != null) {
433 setThumbPos(w, thumb, getScale(), thumbOffset);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700434 }
435 }
436
Alan Viverette661e6362014-05-12 10:55:37 -0700437 private float getScale() {
438 final int max = getMax();
439 return max > 0 ? getProgress() / (float) max : 0;
440 }
441
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700442 /**
Alan Viverette661e6362014-05-12 10:55:37 -0700443 * Updates the thumb drawable bounds.
444 *
445 * @param w Width of the view, including padding
446 * @param thumb Drawable used for the thumb
447 * @param scale Current progress between 0 and 1
448 * @param offset Vertical offset for centering. If set to
449 * {@link Integer#MIN_VALUE}, the current offset will be used.
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700450 */
Alan Viverette661e6362014-05-12 10:55:37 -0700451 private void setThumbPos(int w, Drawable thumb, float scale, int offset) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700452 int available = w - mPaddingLeft - mPaddingRight;
Alan Viverette61956602014-04-22 19:07:06 -0700453 final int thumbWidth = thumb.getIntrinsicWidth();
454 final int thumbHeight = thumb.getIntrinsicHeight();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700455 available -= thumbWidth;
456
457 // The extra space for the thumb to move on the track
458 available += mThumbOffset * 2;
459
Alan Viverette61956602014-04-22 19:07:06 -0700460 final int thumbPos = (int) (scale * available + 0.5f);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700461
Alan Viverette61956602014-04-22 19:07:06 -0700462 final int top, bottom;
Alan Viverette661e6362014-05-12 10:55:37 -0700463 if (offset == Integer.MIN_VALUE) {
Alan Viverette61956602014-04-22 19:07:06 -0700464 final Rect oldBounds = thumb.getBounds();
465 top = oldBounds.top;
466 bottom = oldBounds.bottom;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700467 } else {
Alan Viverette661e6362014-05-12 10:55:37 -0700468 top = offset;
469 bottom = offset + thumbHeight;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700470 }
Alan Viverette61956602014-04-22 19:07:06 -0700471
Fabrice Di Meglio2b378cd2013-01-30 16:39:33 -0800472 final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
Alan Viverette61956602014-04-22 19:07:06 -0700473 final int right = left + thumbWidth;
474
475 final Drawable background = getBackground();
Alan Viverettec80ad992014-05-19 15:46:17 -0700476 if (background != null) {
Alan Viverette61956602014-04-22 19:07:06 -0700477 final int offsetX = mPaddingLeft - mThumbOffset;
478 final int offsetY = mPaddingTop;
Alan Viverette30462412014-06-18 17:54:58 -0700479 background.setHotspotBounds(left + offsetX, top + offsetY,
480 right + offsetX, bottom + offsetY);
Alan Viverette61956602014-04-22 19:07:06 -0700481 }
482
483 // Canvas will be translated, so 0,0 is where we start drawing
484 thumb.setBounds(left, top, right, bottom);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700485 }
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700486
Fabrice Di Meglio4457e852012-09-18 19:23:12 -0700487 /**
488 * @hide
489 */
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700490 @Override
491 public void onResolveDrawables(int layoutDirection) {
492 super.onResolveDrawables(layoutDirection);
493
494 if (mThumb != null) {
495 mThumb.setLayoutDirection(layoutDirection);
496 }
497 }
498
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700499 @Override
500 protected synchronized void onDraw(Canvas canvas) {
501 super.onDraw(canvas);
Alan Viverette661e6362014-05-12 10:55:37 -0700502 drawThumb(canvas);
Alan Viverette8de14942014-06-18 18:05:15 -0700503
Alan Viverette661e6362014-05-12 10:55:37 -0700504 }
505
506 @Override
507 void drawTrack(Canvas canvas) {
508 final Drawable thumbDrawable = mThumb;
509 if (thumbDrawable != null && mSplitTrack) {
510 final Insets insets = thumbDrawable.getOpticalInsets();
511 final Rect tempRect = mTempRect;
512 thumbDrawable.copyBounds(tempRect);
513 tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
514 tempRect.left += insets.left;
515 tempRect.right -= insets.right;
516
517 final int saveCount = canvas.save();
518 canvas.clipRect(tempRect, Op.DIFFERENCE);
519 super.drawTrack(canvas);
520 canvas.restoreToCount(saveCount);
521 } else {
522 super.drawTrack(canvas);
523 }
524 }
525
526 /**
527 * Draw the thumb.
528 */
529 void drawThumb(Canvas canvas) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700530 if (mThumb != null) {
531 canvas.save();
532 // Translate the padding. For the x, we need to allow the thumb to
533 // draw in its extra space
534 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
535 mThumb.draw(canvas);
536 canvas.restore();
537 }
538 }
539
540 @Override
541 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
542 Drawable d = getCurrentDrawable();
543
544 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
545 int dw = 0;
546 int dh = 0;
547 if (d != null) {
548 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
549 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
550 dh = Math.max(thumbHeight, dh);
551 }
552 dw += mPaddingLeft + mPaddingRight;
553 dh += mPaddingTop + mPaddingBottom;
Alan Viverette661e6362014-05-12 10:55:37 -0700554
Dianne Hackborn189ee182010-12-02 21:48:53 -0800555 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
556 resolveSizeAndState(dh, heightMeasureSpec, 0));
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700557 }
Alan Viverette661e6362014-05-12 10:55:37 -0700558
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700559 @Override
560 public boolean onTouchEvent(MotionEvent event) {
561 if (!mIsUserSeekable || !isEnabled()) {
562 return false;
563 }
Alan Viverette661e6362014-05-12 10:55:37 -0700564
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700565 switch (event.getAction()) {
566 case MotionEvent.ACTION_DOWN:
Adam Powell10298662011-08-14 18:26:30 -0700567 if (isInScrollingContainer()) {
568 mTouchDownX = event.getX();
569 } else {
570 setPressed(true);
Adam Powell2c5b8cc2011-10-13 11:44:38 -0700571 if (mThumb != null) {
572 invalidate(mThumb.getBounds()); // This may be within the padding region
573 }
Adam Powell10298662011-08-14 18:26:30 -0700574 onStartTrackingTouch();
575 trackTouchEvent(event);
576 attemptClaimDrag();
577 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700578 break;
Alan Viverette661e6362014-05-12 10:55:37 -0700579
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700580 case MotionEvent.ACTION_MOVE:
Adam Powell10298662011-08-14 18:26:30 -0700581 if (mIsDragging) {
582 trackTouchEvent(event);
583 } else {
584 final float x = event.getX();
585 if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
586 setPressed(true);
Adam Powell2c5b8cc2011-10-13 11:44:38 -0700587 if (mThumb != null) {
588 invalidate(mThumb.getBounds()); // This may be within the padding region
589 }
Adam Powell10298662011-08-14 18:26:30 -0700590 onStartTrackingTouch();
591 trackTouchEvent(event);
592 attemptClaimDrag();
593 }
594 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700595 break;
Alan Viverette661e6362014-05-12 10:55:37 -0700596
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700597 case MotionEvent.ACTION_UP:
Adam Powell10298662011-08-14 18:26:30 -0700598 if (mIsDragging) {
599 trackTouchEvent(event);
600 onStopTrackingTouch();
601 setPressed(false);
602 } else {
603 // Touch up when we never crossed the touch slop threshold should
604 // be interpreted as a tap-seek to that location.
605 onStartTrackingTouch();
606 trackTouchEvent(event);
607 onStopTrackingTouch();
608 }
Daniel Sandlereb9fdc22009-09-14 13:05:43 -0400609 // ProgressBar doesn't know to repaint the thumb drawable
610 // in its inactive state when the touch stops (because the
611 // value has not apparently changed)
612 invalidate();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700613 break;
Alan Viverette661e6362014-05-12 10:55:37 -0700614
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700615 case MotionEvent.ACTION_CANCEL:
Adam Powell10298662011-08-14 18:26:30 -0700616 if (mIsDragging) {
617 onStopTrackingTouch();
618 setPressed(false);
619 }
Daniel Sandlereb9fdc22009-09-14 13:05:43 -0400620 invalidate(); // see above explanation
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700621 break;
622 }
623 return true;
624 }
625
Alan Viverettec80ad992014-05-19 15:46:17 -0700626 private void setHotspot(float x, float y) {
Alan Viverette61956602014-04-22 19:07:06 -0700627 final Drawable bg = getBackground();
Alan Viverettec80ad992014-05-19 15:46:17 -0700628 if (bg != null) {
629 bg.setHotspot(x, y);
Alan Viverette61956602014-04-22 19:07:06 -0700630 }
631 }
632
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700633 private void trackTouchEvent(MotionEvent event) {
634 final int width = getWidth();
635 final int available = width - mPaddingLeft - mPaddingRight;
Alan Viverette61956602014-04-22 19:07:06 -0700636 final int x = (int) event.getX();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700637 float scale;
638 float progress = 0;
Fabrice Di Meglio2b378cd2013-01-30 16:39:33 -0800639 if (isLayoutRtl() && mMirrorForRtl) {
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700640 if (x > width - mPaddingRight) {
641 scale = 0.0f;
642 } else if (x < mPaddingLeft) {
643 scale = 1.0f;
644 } else {
645 scale = (float)(available - x + mPaddingLeft) / (float)available;
646 progress = mTouchProgressOffset;
647 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700648 } else {
Fabrice Di Meglio0af4b8b2012-06-11 18:30:05 -0700649 if (x < mPaddingLeft) {
650 scale = 0.0f;
651 } else if (x > width - mPaddingRight) {
652 scale = 1.0f;
653 } else {
654 scale = (float)(x - mPaddingLeft) / (float)available;
655 progress = mTouchProgressOffset;
656 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700657 }
Jean-Baptiste Queru69577ae2009-03-09 17:56:44 -0700658 final int max = getMax();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700659 progress += scale * max;
Alan Viverette61956602014-04-22 19:07:06 -0700660
Alan Viverettec80ad992014-05-19 15:46:17 -0700661 setHotspot(x, (int) event.getY());
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700662 setProgress((int) progress, true);
663 }
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800664
665 /**
666 * Tries to claim the user's drag motion, and requests disallowing any
667 * ancestors from stealing events in the drag.
668 */
669 private void attemptClaimDrag() {
670 if (mParent != null) {
671 mParent.requestDisallowInterceptTouchEvent(true);
672 }
673 }
Alan Viverette661e6362014-05-12 10:55:37 -0700674
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700675 /**
676 * This is called when the user has started touching this widget.
677 */
678 void onStartTrackingTouch() {
Adam Powell10298662011-08-14 18:26:30 -0700679 mIsDragging = true;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700680 }
681
682 /**
683 * This is called when the user either releases his touch or the touch is
684 * canceled.
685 */
686 void onStopTrackingTouch() {
Adam Powell10298662011-08-14 18:26:30 -0700687 mIsDragging = false;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700688 }
689
The Android Open Source Project10592532009-03-18 17:39:46 -0700690 /**
691 * Called when the user changes the seekbar's progress by using a key event.
692 */
693 void onKeyChange() {
694 }
695
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800696 @Override
697 public boolean onKeyDown(int keyCode, KeyEvent event) {
Romain Guy992cc422010-01-04 15:39:40 -0800698 if (isEnabled()) {
Johan Redestig575bb382013-01-18 18:21:48 +0100699 int increment = mKeyProgressIncrement;
Romain Guy992cc422010-01-04 15:39:40 -0800700 switch (keyCode) {
701 case KeyEvent.KEYCODE_DPAD_LEFT:
Johan Redestig575bb382013-01-18 18:21:48 +0100702 increment = -increment;
703 // fallthrough
Romain Guy992cc422010-01-04 15:39:40 -0800704 case KeyEvent.KEYCODE_DPAD_RIGHT:
Johan Redestig575bb382013-01-18 18:21:48 +0100705 increment = isLayoutRtl() ? -increment : increment;
706 int progress = getProgress() + increment;
Alan Viverette73d28902014-12-02 13:58:27 -0800707 if (progress > 0 && progress < getMax()) {
708 setProgress(progress, true);
Johan Redestig575bb382013-01-18 18:21:48 +0100709 onKeyChange();
710 return true;
711 }
Alan Viverette73d28902014-12-02 13:58:27 -0800712 break;
Romain Guy992cc422010-01-04 15:39:40 -0800713 }
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800714 }
715
716 return super.onKeyDown(keyCode, event);
717 }
718
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800719 @Override
Dianne Hackborna7bb6fb2015-02-03 18:13:40 -0800720 public CharSequence getAccessibilityClassName() {
721 return AbsSeekBar.class.getName();
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800722 }
723
Alan Viverettea54956a2015-01-07 16:05:02 -0800724 /** @hide */
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800725 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800726 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
727 super.onInitializeAccessibilityNodeInfoInternal(info);
alanvc826b7d2012-05-16 14:19:21 -0700728
729 if (isEnabled()) {
730 final int progress = getProgress();
731 if (progress > 0) {
732 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
733 }
734 if (progress < getMax()) {
735 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
736 }
737 }
738 }
739
Alan Viverettea54956a2015-01-07 16:05:02 -0800740 /** @hide */
alanvc826b7d2012-05-16 14:19:21 -0700741 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800742 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
743 if (super.performAccessibilityActionInternal(action, arguments)) {
alanvc826b7d2012-05-16 14:19:21 -0700744 return true;
745 }
746 if (!isEnabled()) {
747 return false;
748 }
749 final int progress = getProgress();
750 final int increment = Math.max(1, Math.round((float) getMax() / 5));
751 switch (action) {
752 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
753 if (progress <= 0) {
754 return false;
755 }
756 setProgress(progress - increment, true);
757 onKeyChange();
758 return true;
759 }
760 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
761 if (progress >= getMax()) {
762 return false;
763 }
764 setProgress(progress + increment, true);
765 onKeyChange();
766 return true;
767 }
768 }
769 return false;
Svetoslav Ganov8a78fd42012-01-17 14:36:46 -0800770 }
Fabrice Di Megliof37df1b2012-10-15 19:10:08 -0700771
772 @Override
773 public void onRtlPropertiesChanged(int layoutDirection) {
774 super.onRtlPropertiesChanged(layoutDirection);
775
Alan Viverette661e6362014-05-12 10:55:37 -0700776 final Drawable thumb = mThumb;
Fabrice Di Megliof37df1b2012-10-15 19:10:08 -0700777 if (thumb != null) {
Alan Viverette661e6362014-05-12 10:55:37 -0700778 setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE);
779
780 // Since we draw translated, the drawable's bounds that it signals
781 // for invalidation won't be the actual bounds we want invalidated,
782 // so just invalidate this whole view.
Fabrice Di Megliof37df1b2012-10-15 19:10:08 -0700783 invalidate();
784 }
785 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700786}