AbsSeekBar.java revision b798689749c64baba81f02e10cf2157c747d6b46
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
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;
25import android.view.KeyEvent;
26import 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;
43
44    private static final int NO_ALPHA = 0xFF;
45    float mDisabledAlpha;
46
47    public AbsSeekBar(Context context) {
48        super(context);
49    }
50
51    public AbsSeekBar(Context context, AttributeSet attrs) {
52        super(context, attrs);
53    }
54
55    public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) {
56        super(context, attrs, defStyle);
57
58        TypedArray a = context.obtainStyledAttributes(attrs,
59                com.android.internal.R.styleable.SeekBar, defStyle, 0);
60        Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
61        setThumb(thumb);
62        int thumbOffset =
63                a.getDimensionPixelOffset(com.android.internal.R.styleable.SeekBar_thumbOffset, 0);
64        setThumbOffset(thumbOffset);
65        a.recycle();
66
67        a = context.obtainStyledAttributes(attrs,
68                com.android.internal.R.styleable.Theme, 0, 0);
69        mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
70        a.recycle();
71    }
72
73    /**
74     * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar
75     *
76     * @param thumb Drawable representing the thumb
77     */
78    public void setThumb(Drawable thumb) {
79        if (thumb != null) {
80            thumb.setCallback(this);
81        }
82        mThumb = thumb;
83        invalidate();
84    }
85
86    /**
87     * @see #setThumbOffset(int)
88     */
89    public int getThumbOffset() {
90        return mThumbOffset;
91    }
92
93    /**
94     * Sets the thumb offset that allows the thumb to extend out of the range of
95     * the track.
96     *
97     * @param thumbOffset The offset amount in pixels.
98     */
99    public void setThumbOffset(int thumbOffset) {
100        mThumbOffset = thumbOffset;
101        invalidate();
102    }
103
104    @Override
105    protected boolean verifyDrawable(Drawable who) {
106        return who == mThumb || super.verifyDrawable(who);
107    }
108
109    @Override
110    protected void drawableStateChanged() {
111        super.drawableStateChanged();
112
113        Drawable progressDrawable = getProgressDrawable();
114        if (progressDrawable != null) {
115            progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
116        }
117
118        if (mThumb != null && mThumb.isStateful()) {
119            int[] state = getDrawableState();
120            mThumb.setState(state);
121        }
122    }
123
124    @Override
125    void onProgressRefresh(float scale, boolean fromUser) {
126        Drawable thumb = mThumb;
127        if (thumb != null) {
128            setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE);
129            /*
130             * Since we draw translated, the drawable's bounds that it signals
131             * for invalidation won't be the actual bounds we want invalidated,
132             * so just invalidate this whole view.
133             */
134            invalidate();
135        }
136    }
137
138
139    @Override
140    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
141        Drawable d = getCurrentDrawable();
142        Drawable thumb = mThumb;
143        int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
144        // The max height does not incorporate padding, whereas the height
145        // parameter does
146        int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
147
148        int max = getMax();
149        float scale = max > 0 ? (float) getProgress() / (float) max : 0;
150
151        if (thumbHeight > trackHeight) {
152            if (thumb != null) {
153                setThumbPos(w, h, thumb, scale, 0);
154            }
155            int gapForCenteringTrack = (thumbHeight - trackHeight) / 2;
156            if (d != null) {
157                // Canvas will be translated by the padding, so 0,0 is where we start drawing
158                d.setBounds(0, gapForCenteringTrack,
159                        w - mPaddingRight - mPaddingLeft, h - mPaddingBottom - gapForCenteringTrack
160                        - mPaddingTop);
161            }
162        } else {
163            if (d != null) {
164                // Canvas will be translated by the padding, so 0,0 is where we start drawing
165                d.setBounds(0, 0, w - mPaddingRight - mPaddingLeft, h - mPaddingBottom
166                        - mPaddingTop);
167            }
168            int gap = (trackHeight - thumbHeight) / 2;
169            if (thumb != null) {
170                setThumbPos(w, h, thumb, scale, gap);
171            }
172        }
173    }
174
175    /**
176     * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and
177     *            the old vertical bounds will be used.
178     */
179    private void setThumbPos(int w, int h, Drawable thumb, float scale, int gap) {
180        int available = w - mPaddingLeft - mPaddingRight;
181        int thumbWidth = thumb.getIntrinsicWidth();
182        int thumbHeight = thumb.getIntrinsicHeight();
183        available -= thumbWidth;
184
185        // The extra space for the thumb to move on the track
186        available += mThumbOffset * 2;
187
188        int thumbPos = (int) (scale * available);
189
190        int topBound, bottomBound;
191        if (gap == Integer.MIN_VALUE) {
192            Rect oldBounds = thumb.getBounds();
193            topBound = oldBounds.top;
194            bottomBound = oldBounds.bottom;
195        } else {
196            topBound = gap;
197            bottomBound = gap + thumbHeight;
198        }
199
200        // Canvas will be translated, so 0,0 is where we start drawing
201        thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
202    }
203
204    @Override
205    protected synchronized void onDraw(Canvas canvas) {
206        super.onDraw(canvas);
207        if (mThumb != null) {
208            canvas.save();
209            // Translate the padding. For the x, we need to allow the thumb to
210            // draw in its extra space
211            canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
212            mThumb.draw(canvas);
213            canvas.restore();
214        }
215    }
216
217    @Override
218    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
219        Drawable d = getCurrentDrawable();
220
221        int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
222        int dw = 0;
223        int dh = 0;
224        if (d != null) {
225            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
226            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
227            dh = Math.max(thumbHeight, dh);
228        }
229        dw += mPaddingLeft + mPaddingRight;
230        dh += mPaddingTop + mPaddingBottom;
231
232        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
233                resolveSize(dh, heightMeasureSpec));
234    }
235
236    @Override
237    public boolean onTouchEvent(MotionEvent event) {
238        if (!mIsUserSeekable || !isEnabled()) {
239            return false;
240        }
241
242        switch (event.getAction()) {
243            case MotionEvent.ACTION_DOWN:
244                setPressed(true);
245                onStartTrackingTouch();
246                trackTouchEvent(event);
247                break;
248
249            case MotionEvent.ACTION_MOVE:
250                trackTouchEvent(event);
251                attemptClaimDrag();
252                break;
253
254            case MotionEvent.ACTION_UP:
255                trackTouchEvent(event);
256                onStopTrackingTouch();
257                setPressed(false);
258                break;
259
260            case MotionEvent.ACTION_CANCEL:
261                onStopTrackingTouch();
262                setPressed(false);
263                break;
264        }
265        return true;
266    }
267
268    private void trackTouchEvent(MotionEvent event) {
269        final int width = getWidth();
270        final int available = width - mPaddingLeft - mPaddingRight;
271        int x = (int)event.getX();
272        float scale;
273        float progress = 0;
274        if (x < mPaddingLeft) {
275            scale = 0.0f;
276        } else if (x > width - mPaddingRight) {
277            scale = 1.0f;
278        } else {
279            scale = (float)(x - mPaddingLeft) / (float)available;
280            progress = mTouchProgressOffset;
281        }
282
283        final int max = getMax();
284        progress += scale * max;
285        if (progress < 0) {
286            progress = 0;
287        } else if (progress > max) {
288            progress = max;
289        }
290
291        setProgress((int) progress, true);
292    }
293
294    /**
295     * Tries to claim the user's drag motion, and requests disallowing any
296     * ancestors from stealing events in the drag.
297     */
298    private void attemptClaimDrag() {
299        if (mParent != null) {
300            mParent.requestDisallowInterceptTouchEvent(true);
301        }
302    }
303
304    /**
305     * This is called when the user has started touching this widget.
306     */
307    void onStartTrackingTouch() {
308    }
309
310    /**
311     * This is called when the user either releases his touch or the touch is
312     * canceled.
313     */
314    void onStopTrackingTouch() {
315    }
316
317    @Override
318    public boolean onKeyDown(int keyCode, KeyEvent event) {
319        int progress = getProgress();
320
321        switch (keyCode) {
322            case KeyEvent.KEYCODE_DPAD_LEFT:
323                if (progress <= 0) break;
324                setProgress(progress - 1, true);
325                return true;
326
327            case KeyEvent.KEYCODE_DPAD_RIGHT:
328                if (progress >= getMax()) break;
329                setProgress(progress + 1, true);
330                return true;
331        }
332
333        return super.onKeyDown(keyCode, event);
334    }
335
336}
337