• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.KeyEvent;
26 import android.view.MotionEvent;
27 
28 public abstract class AbsSeekBar extends ProgressBar {
29     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;
42 
43     /**
44      * On key presses (right or left), the amount to increment/decrement the
45      * progress.
46      */
47     private int mKeyProgressIncrement = 1;
48 
49     private static final int NO_ALPHA = 0xFF;
50     private float mDisabledAlpha;
51 
AbsSeekBar(Context context)52     public AbsSeekBar(Context context) {
53         super(context);
54     }
55 
AbsSeekBar(Context context, AttributeSet attrs)56     public AbsSeekBar(Context context, AttributeSet attrs) {
57         super(context, attrs);
58     }
59 
AbsSeekBar(Context context, AttributeSet attrs, int defStyle)60     public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
61         super(context, attrs, defStyle);
62 
63         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);
66         setThumb(thumb); // will guess mThumbOffset if thumb != null...
67         // ...but allow layout to override this
68         int thumbOffset = a.getDimensionPixelOffset(
69                 com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
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      * <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)).
84      *
85      * @param thumb Drawable representing the thumb
86      */
setThumb(Drawable thumb)87     public void setThumb(Drawable thumb) {
88         if (thumb != null) {
89             thumb.setCallback(this);
90 
91             // Assuming the thumb drawable is symmetric, set the thumb offset
92             // such that the thumb will hang halfway off either edge of the
93             // progress bar.
94             mThumbOffset = thumb.getIntrinsicWidth() / 2;
95         }
96         mThumb = thumb;
97         invalidate();
98     }
99 
100     /**
101      * @see #setThumbOffset(int)
102      */
getThumbOffset()103     public int getThumbOffset() {
104         return mThumbOffset;
105     }
106 
107     /**
108      * Sets the thumb offset that allows the thumb to extend out of the range of
109      * the track.
110      *
111      * @param thumbOffset The offset amount in pixels.
112      */
setThumbOffset(int thumbOffset)113     public void setThumbOffset(int thumbOffset) {
114         mThumbOffset = thumbOffset;
115         invalidate();
116     }
117 
118     /**
119      * Sets the amount of progress changed via the arrow keys.
120      *
121      * @param increment The amount to increment or decrement when the user
122      *            presses the arrow keys.
123      */
setKeyProgressIncrement(int increment)124     public void setKeyProgressIncrement(int increment) {
125         mKeyProgressIncrement = increment < 0 ? -increment : increment;
126     }
127 
128     /**
129      * Returns the amount of progress changed via the arrow keys.
130      * <p>
131      * By default, this will be a value that is derived from the max progress.
132      *
133      * @return The amount to increment or decrement when the user presses the
134      *         arrow keys. This will be positive.
135      */
136     public int getKeyProgressIncrement() {
137         return mKeyProgressIncrement;
138     }
139 
140     @Override
141     public synchronized void setMax(int max) {
142         super.setMax(max);
143 
144         if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
145             // It will take the user too long to change this via keys, change it
146             // to something more reasonable
147             setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
148         }
149     }
150 
151     @Override
152     protected boolean verifyDrawable(Drawable who) {
153         return who == mThumb || super.verifyDrawable(who);
154     }
155 
156     @Override
157     protected void drawableStateChanged() {
158         super.drawableStateChanged();
159 
160         Drawable progressDrawable = getProgressDrawable();
161         if (progressDrawable != null) {
162             progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
163         }
164 
165         if (mThumb != null && mThumb.isStateful()) {
166             int[] state = getDrawableState();
167             mThumb.setState(state);
168         }
169     }
170 
171     @Override
172     void onProgressRefresh(float scale, boolean fromUser) {
173         Drawable thumb = mThumb;
174         if (thumb != null) {
175             setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
176             /*
177              * Since we draw translated, the drawable's bounds that it signals
178              * for invalidation won't be the actual bounds we want invalidated,
179              * so just invalidate this whole view.
180              */
181             invalidate();
182         }
183     }
184 
185 
186     @Override
187     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
188         Drawable d = getCurrentDrawable();
189         Drawable thumb = mThumb;
190         int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
191         // The max height does not incorporate padding, whereas the height
192         // parameter does
193         int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
194 
195         int max = getMax();
196         float scale = max > 0 ? (float) getProgress() / (float) max : 0;
197 
198         if (thumbHeight > trackHeight) {
199             if (thumb != null) {
200                 setThumbPos(w, thumb, scale, 0);
201             }
202             int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
203             if (d != null) {
204                 // Canvas will be translated by the padding, so 0,0 is where we start drawing
205                 d.setBounds(0, gapForCenteringTrack,
206                         w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
207                         - mPaddingTop);
208             }
209         } else {
210             if (d != null) {
211                 // Canvas will be translated by the padding, so 0,0 is where we start drawing
212                 d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
213                         - mPaddingTop);
214             }
215             int gap = (trackHeight - thumbHeight) / 2;
216             if (thumb != null) {
217                 setThumbPos(w, thumb, scale, gap);
218             }
219         }
220     }
221 
222     /**
223      * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
224      */
225     private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
226         int available = w - mPaddingLeft - mPaddingRight;
227         int thumbWidth = thumb.getIntrinsicWidth();
228         int thumbHeight = thumb.getIntrinsicHeight();
229         available -= thumbWidth;
230 
231         // The extra space for the thumb to move on the track
232         available += mThumbOffset * 2;
233 
234         int thumbPos = (int) (scale * available);
235 
236         int topBound, bottomBound;
237         if (gap == Integer.MIN_VALUE) {
238             Rect oldBounds = thumb.getBounds();
239             topBound = oldBounds.top;
240             bottomBound = oldBounds.bottom;
241         } else {
242             topBound = gap;
243             bottomBound = gap + thumbHeight;
244         }
245 
246         // Canvas will be translated, so 0,0 is where we start drawing
247         thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
248     }
249 
250     @Override
251     protected synchronized void onDraw(Canvas canvas) {
252         super.onDraw(canvas);
253         if (mThumb != null) {
254             canvas.save();
255             // Translate the padding. For the x, we need to allow the thumb to
256             // draw in its extra space
257             canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
258             mThumb.draw(canvas);
259             canvas.restore();
260         }
261     }
262 
263     @Override
264     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
265         Drawable d = getCurrentDrawable();
266 
267         int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
268         int dw = 0;
269         int dh = 0;
270         if (d != null) {
271             dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
272             dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
273             dh = Math.max(thumbHeight, dh);
274         }
275         dw += mPaddingLeft + mPaddingRight;
276         dh += mPaddingTop + mPaddingBottom;
277 
278         setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
279                 resolveSize(dh, heightMeasureSpec));
280     }
281 
282     @Override
283     public boolean onTouchEvent(MotionEvent event) {
284         if (!mIsUserSeekable || !isEnabled()) {
285             return false;
286         }
287 
288         switch (event.getAction()) {
289             case MotionEvent.ACTION_DOWN:
290                 setPressed(true);
291                 onStartTrackingTouch();
292                 trackTouchEvent(event);
293                 break;
294 
295             case MotionEvent.ACTION_MOVE:
296                 trackTouchEvent(event);
297                 attemptClaimDrag();
298                 break;
299 
300             case MotionEvent.ACTION_UP:
301                 trackTouchEvent(event);
302                 onStopTrackingTouch();
303                 setPressed(false);
304                 // ProgressBar doesn't know to repaint the thumb drawable
305                 // in its inactive state when the touch stops (because the
306                 // value has not apparently changed)
307                 invalidate();
308                 break;
309 
310             case MotionEvent.ACTION_CANCEL:
311                 onStopTrackingTouch();
312                 setPressed(false);
313                 invalidate(); // see above explanation
314                 break;
315         }
316         return true;
317     }
318 
319     private void trackTouchEvent(MotionEvent event) {
320         final int width = getWidth();
321         final int available = width - mPaddingLeft - mPaddingRight;
322         int x = (int)event.getX();
323         float scale;
324         float progress = 0;
325         if (x < mPaddingLeft) {
326             scale = 0.0f;
327         } else if (x > width - mPaddingRight) {
328             scale = 1.0f;
329         } else {
330             scale = (float)(x - mPaddingLeft) / (float)available;
331             progress = mTouchProgressOffset;
332         }
333 
334         final int max = getMax();
335         progress += scale * max;
336 
337         setProgress((int) progress, true);
338     }
339 
340     /**
341      * Tries to claim the user's drag motion, and requests disallowing any
342      * ancestors from stealing events in the drag.
343      */
344     private void attemptClaimDrag() {
345         if (mParent != null) {
346             mParent.requestDisallowInterceptTouchEvent(true);
347         }
348     }
349 
350     /**
351      * This is called when the user has started touching this widget.
352      */
353     void onStartTrackingTouch() {
354     }
355 
356     /**
357      * This is called when the user either releases his touch or the touch is
358      * canceled.
359      */
360     void onStopTrackingTouch() {
361     }
362 
363     /**
364      * Called when the user changes the seekbar's progress by using a key event.
365      */
366     void onKeyChange() {
367     }
368 
369     @Override
370     public boolean onKeyDown(int keyCode, KeyEvent event) {
371         if (isEnabled()) {
372             int progress = getProgress();
373             switch (keyCode) {
374                 case KeyEvent.KEYCODE_DPAD_LEFT:
375                     if (progress <= 0) break;
376                     setProgress(progress - mKeyProgressIncrement, true);
377                     onKeyChange();
378                     return true;
379 
380                 case KeyEvent.KEYCODE_DPAD_RIGHT:
381                     if (progress >= getMax()) break;
382                     setProgress(progress + mKeyProgressIncrement, true);
383                     onKeyChange();
384                     return true;
385             }
386         }
387 
388         return super.onKeyDown(keyCode, event);
389     }
390 
391 }
392