blob: 0da73a49c979e51ee2e1cdc356eae603051f1aa4 [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
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Rect;
23import android.graphics.drawable.Drawable;
24import android.util.AttributeSet;
The Android Open Source Projectb7986892009-01-09 17:51:23 -080025import android.view.KeyEvent;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070026import android.view.MotionEvent;
27
28public abstract class AbsSeekBar extends ProgressBar {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070029 private Drawable mThumb;
30 private int mThumbOffset;
31
32 /**
33 * On touch, this offset plus the scaled value from the position of the
34 * touch will form the progress value. Usually 0.
35 */
36 float mTouchProgressOffset;
37
38 /**
39 * Whether this is user seekable.
40 */
41 boolean mIsUserSeekable = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
43 /**
44 * On key presses (right or left), the amount to increment/decrement the
45 * progress.
46 */
47 private int mKeyProgressIncrement = 1;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070048
49 private static final int NO_ALPHA = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050 private float mDisabledAlpha;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070051
52 public AbsSeekBar(Context context) {
53 super(context);
54 }
55
56 public AbsSeekBar(Context context, AttributeSet attrs) {
57 super(context, attrs);
58 }
59
60 public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
61 super(context, attrs, defStyle);
62
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070063 TypedArray a = context.obtainStyledAttributes(attrs,
64 com.android.internal.R.styleable.SeekBar, defStyle, 0);
65 Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
Daniel Sandler8d28c3b2009-08-20 13:34:02 -040066 setThumb(thumb); // will guess mThumbOffset if thumb != null...
67 // ...but allow layout to override this
Romain Guy992cc422010-01-04 15:39:40 -080068 int thumbOffset = a.getDimensionPixelOffset(
69 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
70 setThumbOffset(thumbOffset);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070071 a.recycle();
72
73 a = context.obtainStyledAttributes(attrs,
74 com.android.internal.R.styleable.Theme, 0, 0);
75 mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
76 a.recycle();
77 }
78
79 /**
Daniel Sandler8d28c3b2009-08-20 13:34:02 -040080 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar.
81 * <p>
82 * If the thumb is a valid drawable (i.e. not null), half its width will be
83 * used as the new thumb offset (@see #setThumbOffset(int)).
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070084 *
85 * @param thumb Drawable representing the thumb
86 */
87 public void setThumb(Drawable thumb) {
Joe Onorato2e585f72010-12-02 14:10:57 -080088 boolean needUpdate;
89 // This way, calling setThumb again with the same bitmap will result in
90 // it recalcuating mThumbOffset (if for example it the bounds of the
91 // drawable changed)
92 if (mThumb != null && thumb != mThumb) {
93 mThumb.setCallback(null);
94 needUpdate = true;
95 } else {
96 needUpdate = false;
97 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070098 if (thumb != null) {
99 thumb.setCallback(this);
Daniel Sandler8d28c3b2009-08-20 13:34:02 -0400100
101 // Assuming the thumb drawable is symmetric, set the thumb offset
102 // such that the thumb will hang halfway off either edge of the
103 // progress bar.
Romain Guy992cc422010-01-04 15:39:40 -0800104 mThumbOffset = thumb.getIntrinsicWidth() / 2;
Joe Onorato2e585f72010-12-02 14:10:57 -0800105
106 // If we're updating get the new states
107 if (needUpdate &&
108 (thumb.getIntrinsicWidth() != mThumb.getIntrinsicWidth()
109 || thumb.getIntrinsicHeight() != mThumb.getIntrinsicHeight())) {
110 requestLayout();
111 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700112 }
113 mThumb = thumb;
114 invalidate();
Joe Onorato2e585f72010-12-02 14:10:57 -0800115 if (needUpdate) {
116 updateThumbPos(getWidth(), getHeight());
117 if (thumb.isStateful()) {
118 // Note that if the states are different this won't work.
119 // For now, let's consider that an app bug.
120 int[] state = getDrawableState();
121 thumb.setState(state);
122 }
123 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700124 }
125
126 /**
127 * @see #setThumbOffset(int)
128 */
129 public int getThumbOffset() {
130 return mThumbOffset;
131 }
132
133 /**
134 * Sets the thumb offset that allows the thumb to extend out of the range of
135 * the track.
136 *
137 * @param thumbOffset The offset amount in pixels.
138 */
139 public void setThumbOffset(int thumbOffset) {
140 mThumbOffset = thumbOffset;
141 invalidate();
142 }
143
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 /**
145 * Sets the amount of progress changed via the arrow keys.
146 *
147 * @param increment The amount to increment or decrement when the user
148 * presses the arrow keys.
149 */
150 public void setKeyProgressIncrement(int increment) {
151 mKeyProgressIncrement = increment < 0 ? -increment : increment;
152 }
153
154 /**
155 * Returns the amount of progress changed via the arrow keys.
156 * <p>
157 * By default, this will be a value that is derived from the max progress.
158 *
159 * @return The amount to increment or decrement when the user presses the
160 * arrow keys. This will be positive.
161 */
162 public int getKeyProgressIncrement() {
163 return mKeyProgressIncrement;
164 }
165
166 @Override
167 public synchronized void setMax(int max) {
168 super.setMax(max);
169
170 if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
171 // It will take the user too long to change this via keys, change it
172 // to something more reasonable
173 setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
174 }
175 }
176
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700177 @Override
178 protected boolean verifyDrawable(Drawable who) {
179 return who == mThumb || super.verifyDrawable(who);
180 }
181
182 @Override
Dianne Hackborne2136772010-11-04 15:08:59 -0700183 public void jumpDrawablesToCurrentState() {
184 super.jumpDrawablesToCurrentState();
185 if (mThumb != null) mThumb.jumpToCurrentState();
186 }
187
188 @Override
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700189 protected void drawableStateChanged() {
190 super.drawableStateChanged();
191
192 Drawable progressDrawable = getProgressDrawable();
193 if (progressDrawable != null) {
194 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
195 }
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800196
197 if (mThumb != null && mThumb.isStateful()) {
198 int[] state = getDrawableState();
199 mThumb.setState(state);
200 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700201 }
202
203 @Override
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800204 void onProgressRefresh(float scale, boolean fromUser) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700205 Drawable thumb = mThumb;
206 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700207 setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700208 /*
209 * Since we draw translated, the drawable's bounds that it signals
210 * for invalidation won't be the actual bounds we want invalidated,
211 * so just invalidate this whole view.
212 */
213 invalidate();
214 }
215 }
216
217
218 @Override
219 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Joe Onorato2e585f72010-12-02 14:10:57 -0800220 updateThumbPos(w, h);
221 }
222
223 private void updateThumbPos(int w, int h) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700224 Drawable d = getCurrentDrawable();
225 Drawable thumb = mThumb;
226 int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
227 // The max height does not incorporate padding, whereas the height
228 // parameter does
229 int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
230
231 int max = getMax();
232 float scale = max > 0 ? (float) getProgress() / (float) max : 0;
233
234 if (thumbHeight > trackHeight) {
235 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700236 setThumbPos(w, thumb, scale, 0);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700237 }
238 int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
239 if (d != null) {
240 // Canvas will be translated by the padding, so 0,0 is where we start drawing
241 d.setBounds(0, gapForCenteringTrack,
242 w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
243 - mPaddingTop);
244 }
245 } else {
246 if (d != null) {
247 // Canvas will be translated by the padding, so 0,0 is where we start drawing
248 d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
249 - mPaddingTop);
250 }
251 int gap = (trackHeight - thumbHeight) / 2;
252 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700253 setThumbPos(w, thumb, scale, gap);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700254 }
255 }
256 }
257
258 /**
259 * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700260 */
The Android Open Source Project10592532009-03-18 17:39:46 -0700261 private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700262 int available = w - mPaddingLeft - mPaddingRight;
263 int thumbWidth = thumb.getIntrinsicWidth();
264 int thumbHeight = thumb.getIntrinsicHeight();
265 available -= thumbWidth;
266
267 // The extra space for the thumb to move on the track
268 available += mThumbOffset * 2;
269
270 int thumbPos = (int) (scale * available);
271
272 int topBound, bottomBound;
273 if (gap == Integer.MIN_VALUE) {
274 Rect oldBounds = thumb.getBounds();
275 topBound = oldBounds.top;
276 bottomBound = oldBounds.bottom;
277 } else {
278 topBound = gap;
279 bottomBound = gap + thumbHeight;
280 }
281
282 // Canvas will be translated, so 0,0 is where we start drawing
283 thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
284 }
285
286 @Override
287 protected synchronized void onDraw(Canvas canvas) {
288 super.onDraw(canvas);
289 if (mThumb != null) {
290 canvas.save();
291 // Translate the padding. For the x, we need to allow the thumb to
292 // draw in its extra space
293 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
294 mThumb.draw(canvas);
295 canvas.restore();
296 }
297 }
298
299 @Override
300 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
301 Drawable d = getCurrentDrawable();
302
303 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
304 int dw = 0;
305 int dh = 0;
306 if (d != null) {
307 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
308 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
309 dh = Math.max(thumbHeight, dh);
310 }
311 dw += mPaddingLeft + mPaddingRight;
312 dh += mPaddingTop + mPaddingBottom;
313
Dianne Hackborn189ee182010-12-02 21:48:53 -0800314 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
315 resolveSizeAndState(dh, heightMeasureSpec, 0));
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700316 }
317
318 @Override
319 public boolean onTouchEvent(MotionEvent event) {
320 if (!mIsUserSeekable || !isEnabled()) {
321 return false;
322 }
323
324 switch (event.getAction()) {
325 case MotionEvent.ACTION_DOWN:
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800326 setPressed(true);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700327 onStartTrackingTouch();
328 trackTouchEvent(event);
329 break;
330
331 case MotionEvent.ACTION_MOVE:
332 trackTouchEvent(event);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800333 attemptClaimDrag();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700334 break;
335
336 case MotionEvent.ACTION_UP:
337 trackTouchEvent(event);
338 onStopTrackingTouch();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800339 setPressed(false);
Daniel Sandlereb9fdc22009-09-14 13:05:43 -0400340 // ProgressBar doesn't know to repaint the thumb drawable
341 // in its inactive state when the touch stops (because the
342 // value has not apparently changed)
343 invalidate();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700344 break;
345
346 case MotionEvent.ACTION_CANCEL:
347 onStopTrackingTouch();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800348 setPressed(false);
Daniel Sandlereb9fdc22009-09-14 13:05:43 -0400349 invalidate(); // see above explanation
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700350 break;
351 }
352 return true;
353 }
354
355 private void trackTouchEvent(MotionEvent event) {
356 final int width = getWidth();
357 final int available = width - mPaddingLeft - mPaddingRight;
358 int x = (int)event.getX();
359 float scale;
360 float progress = 0;
361 if (x < mPaddingLeft) {
362 scale = 0.0f;
363 } else if (x > width - mPaddingRight) {
364 scale = 1.0f;
365 } else {
366 scale = (float)(x - mPaddingLeft) / (float)available;
367 progress = mTouchProgressOffset;
368 }
369
Jean-Baptiste Queru69577ae2009-03-09 17:56:44 -0700370 final int max = getMax();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700371 progress += scale * max;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700372
373 setProgress((int) progress, true);
374 }
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800375
376 /**
377 * Tries to claim the user's drag motion, and requests disallowing any
378 * ancestors from stealing events in the drag.
379 */
380 private void attemptClaimDrag() {
381 if (mParent != null) {
382 mParent.requestDisallowInterceptTouchEvent(true);
383 }
384 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700385
386 /**
387 * This is called when the user has started touching this widget.
388 */
389 void onStartTrackingTouch() {
390 }
391
392 /**
393 * This is called when the user either releases his touch or the touch is
394 * canceled.
395 */
396 void onStopTrackingTouch() {
397 }
398
The Android Open Source Project10592532009-03-18 17:39:46 -0700399 /**
400 * Called when the user changes the seekbar's progress by using a key event.
401 */
402 void onKeyChange() {
403 }
404
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800405 @Override
406 public boolean onKeyDown(int keyCode, KeyEvent event) {
Romain Guy992cc422010-01-04 15:39:40 -0800407 if (isEnabled()) {
408 int progress = getProgress();
409 switch (keyCode) {
410 case KeyEvent.KEYCODE_DPAD_LEFT:
411 if (progress <= 0) break;
412 setProgress(progress - mKeyProgressIncrement, true);
413 onKeyChange();
414 return true;
415
416 case KeyEvent.KEYCODE_DPAD_RIGHT:
417 if (progress >= getMax()) break;
418 setProgress(progress + mKeyProgressIncrement, true);
419 onKeyChange();
420 return true;
421 }
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800422 }
423
424 return super.onKeyDown(keyCode, event);
425 }
426
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700427}