blob: f92eb997d3a4ae549323f34d71e2f6003d1b2b19 [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 {
29
30 private Drawable mThumb;
31 private int mThumbOffset;
32
33 /**
34 * On touch, this offset plus the scaled value from the position of the
35 * touch will form the progress value. Usually 0.
36 */
37 float mTouchProgressOffset;
38
39 /**
40 * Whether this is user seekable.
41 */
42 boolean mIsUserSeekable = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043
44 /**
45 * On key presses (right or left), the amount to increment/decrement the
46 * progress.
47 */
48 private int mKeyProgressIncrement = 1;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070049
50 private static final int NO_ALPHA = 0xFF;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 private float mDisabledAlpha;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070052
53 public AbsSeekBar(Context context) {
54 super(context);
55 }
56
57 public AbsSeekBar(Context context, AttributeSet attrs) {
58 super(context, attrs);
59 }
60
61 public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
62 super(context, attrs, defStyle);
63
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -070064 TypedArray a = context.obtainStyledAttributes(attrs,
65 com.android.internal.R.styleable.SeekBar, defStyle, 0);
66 Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
67 setThumb(thumb);
68 int thumbOffset =
69 a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, 0);
70 setThumbOffset(thumbOffset);
71 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 /**
80 * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar
81 *
82 * @param thumb Drawable representing the thumb
83 */
84 public void setThumb(Drawable thumb) {
85 if (thumb != null) {
86 thumb.setCallback(this);
87 }
88 mThumb = thumb;
89 invalidate();
90 }
91
92 /**
93 * @see #setThumbOffset(int)
94 */
95 public int getThumbOffset() {
96 return mThumbOffset;
97 }
98
99 /**
100 * Sets the thumb offset that allows the thumb to extend out of the range of
101 * the track.
102 *
103 * @param thumbOffset The offset amount in pixels.
104 */
105 public void setThumbOffset(int thumbOffset) {
106 mThumbOffset = thumbOffset;
107 invalidate();
108 }
109
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110 /**
111 * Sets the amount of progress changed via the arrow keys.
112 *
113 * @param increment The amount to increment or decrement when the user
114 * presses the arrow keys.
115 */
116 public void setKeyProgressIncrement(int increment) {
117 mKeyProgressIncrement = increment < 0 ? -increment : increment;
118 }
119
120 /**
121 * Returns the amount of progress changed via the arrow keys.
122 * <p>
123 * By default, this will be a value that is derived from the max progress.
124 *
125 * @return The amount to increment or decrement when the user presses the
126 * arrow keys. This will be positive.
127 */
128 public int getKeyProgressIncrement() {
129 return mKeyProgressIncrement;
130 }
131
132 @Override
133 public synchronized void setMax(int max) {
134 super.setMax(max);
135
136 if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
137 // It will take the user too long to change this via keys, change it
138 // to something more reasonable
139 setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
140 }
141 }
142
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700143 @Override
144 protected boolean verifyDrawable(Drawable who) {
145 return who == mThumb || super.verifyDrawable(who);
146 }
147
148 @Override
149 protected void drawableStateChanged() {
150 super.drawableStateChanged();
151
152 Drawable progressDrawable = getProgressDrawable();
153 if (progressDrawable != null) {
154 progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
155 }
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800156
157 if (mThumb != null && mThumb.isStateful()) {
158 int[] state = getDrawableState();
159 mThumb.setState(state);
160 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700161 }
162
163 @Override
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800164 void onProgressRefresh(float scale, boolean fromUser) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700165 Drawable thumb = mThumb;
166 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700167 setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700168 /*
169 * Since we draw translated, the drawable's bounds that it signals
170 * for invalidation won't be the actual bounds we want invalidated,
171 * so just invalidate this whole view.
172 */
173 invalidate();
174 }
175 }
176
177
178 @Override
179 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
180 Drawable d = getCurrentDrawable();
181 Drawable thumb = mThumb;
182 int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
183 // The max height does not incorporate padding, whereas the height
184 // parameter does
185 int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
186
187 int max = getMax();
188 float scale = max > 0 ? (float) getProgress() / (float) max : 0;
189
190 if (thumbHeight > trackHeight) {
191 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700192 setThumbPos(w, thumb, scale, 0);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700193 }
194 int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
195 if (d != null) {
196 // Canvas will be translated by the padding, so 0,0 is where we start drawing
197 d.setBounds(0, gapForCenteringTrack,
198 w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
199 - mPaddingTop);
200 }
201 } else {
202 if (d != null) {
203 // Canvas will be translated by the padding, so 0,0 is where we start drawing
204 d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
205 - mPaddingTop);
206 }
207 int gap = (trackHeight - thumbHeight) / 2;
208 if (thumb != null) {
The Android Open Source Project10592532009-03-18 17:39:46 -0700209 setThumbPos(w, thumb, scale, gap);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700210 }
211 }
212 }
213
214 /**
215 * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700216 */
The Android Open Source Project10592532009-03-18 17:39:46 -0700217 private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700218 int available = w - mPaddingLeft - mPaddingRight;
219 int thumbWidth = thumb.getIntrinsicWidth();
220 int thumbHeight = thumb.getIntrinsicHeight();
221 available -= thumbWidth;
222
223 // The extra space for the thumb to move on the track
224 available += mThumbOffset * 2;
225
226 int thumbPos = (int) (scale * available);
227
228 int topBound, bottomBound;
229 if (gap == Integer.MIN_VALUE) {
230 Rect oldBounds = thumb.getBounds();
231 topBound = oldBounds.top;
232 bottomBound = oldBounds.bottom;
233 } else {
234 topBound = gap;
235 bottomBound = gap + thumbHeight;
236 }
237
238 // Canvas will be translated, so 0,0 is where we start drawing
239 thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
240 }
241
242 @Override
243 protected synchronized void onDraw(Canvas canvas) {
244 super.onDraw(canvas);
245 if (mThumb != null) {
246 canvas.save();
247 // Translate the padding. For the x, we need to allow the thumb to
248 // draw in its extra space
249 canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
250 mThumb.draw(canvas);
251 canvas.restore();
252 }
253 }
254
255 @Override
256 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
257 Drawable d = getCurrentDrawable();
258
259 int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
260 int dw = 0;
261 int dh = 0;
262 if (d != null) {
263 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
264 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
265 dh = Math.max(thumbHeight, dh);
266 }
267 dw += mPaddingLeft + mPaddingRight;
268 dh += mPaddingTop + mPaddingBottom;
269
270 setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
271 resolveSize(dh, heightMeasureSpec));
272 }
273
274 @Override
275 public boolean onTouchEvent(MotionEvent event) {
276 if (!mIsUserSeekable || !isEnabled()) {
277 return false;
278 }
279
280 switch (event.getAction()) {
281 case MotionEvent.ACTION_DOWN:
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800282 setPressed(true);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700283 onStartTrackingTouch();
284 trackTouchEvent(event);
285 break;
286
287 case MotionEvent.ACTION_MOVE:
288 trackTouchEvent(event);
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800289 attemptClaimDrag();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700290 break;
291
292 case MotionEvent.ACTION_UP:
293 trackTouchEvent(event);
294 onStopTrackingTouch();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800295 setPressed(false);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700296 break;
297
298 case MotionEvent.ACTION_CANCEL:
299 onStopTrackingTouch();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800300 setPressed(false);
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700301 break;
302 }
303 return true;
304 }
305
306 private void trackTouchEvent(MotionEvent event) {
307 final int width = getWidth();
308 final int available = width - mPaddingLeft - mPaddingRight;
309 int x = (int)event.getX();
310 float scale;
311 float progress = 0;
312 if (x < mPaddingLeft) {
313 scale = 0.0f;
314 } else if (x > width - mPaddingRight) {
315 scale = 1.0f;
316 } else {
317 scale = (float)(x - mPaddingLeft) / (float)available;
318 progress = mTouchProgressOffset;
319 }
320
Jean-Baptiste Queru69577ae2009-03-09 17:56:44 -0700321 final int max = getMax();
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700322 progress += scale * max;
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700323
324 setProgress((int) progress, true);
325 }
The Android Open Source Projectf013e1a2008-12-17 18:05:43 -0800326
327 /**
328 * Tries to claim the user's drag motion, and requests disallowing any
329 * ancestors from stealing events in the drag.
330 */
331 private void attemptClaimDrag() {
332 if (mParent != null) {
333 mParent.requestDisallowInterceptTouchEvent(true);
334 }
335 }
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700336
337 /**
338 * This is called when the user has started touching this widget.
339 */
340 void onStartTrackingTouch() {
341 }
342
343 /**
344 * This is called when the user either releases his touch or the touch is
345 * canceled.
346 */
347 void onStopTrackingTouch() {
348 }
349
The Android Open Source Project10592532009-03-18 17:39:46 -0700350 /**
351 * Called when the user changes the seekbar's progress by using a key event.
352 */
353 void onKeyChange() {
354 }
355
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800356 @Override
357 public boolean onKeyDown(int keyCode, KeyEvent event) {
358 int progress = getProgress();
359
360 switch (keyCode) {
361 case KeyEvent.KEYCODE_DPAD_LEFT:
362 if (progress <= 0) break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 setProgress(progress - mKeyProgressIncrement, true);
The Android Open Source Project10592532009-03-18 17:39:46 -0700364 onKeyChange();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800365 return true;
366
367 case KeyEvent.KEYCODE_DPAD_RIGHT:
368 if (progress >= getMax()) break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 setProgress(progress + mKeyProgressIncrement, true);
The Android Open Source Project10592532009-03-18 17:39:46 -0700370 onKeyChange();
The Android Open Source Projectb7986892009-01-09 17:51:23 -0800371 return true;
372 }
373
374 return super.onKeyDown(keyCode, event);
375 }
376
The Android Open Source Project54b6cfa2008-10-21 07:00:00 -0700377}