OverScroller.java revision 3fc3737ceb0f5c3b086472fb2cf7ebfb089e1bc8
1/*
2 * Copyright (C) 2006 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.view.animation.AccelerateDecelerateInterpolator;
21import android.view.animation.DecelerateInterpolator;
22
23/**
24 * This class encapsulates scrolling with the ability to overshoot the bounds
25 * of a scrolling operation. This class attempts to be a drop-in replacement
26 * for {@link android.widget.Scroller} in most cases.
27 *
28 * @hide Pending API approval
29 */
30public class OverScroller {
31    private static final int SPRINGBACK_DURATION = 150;
32    private static final int OVERFLING_DURATION = 150;
33
34    private static final int MODE_DEFAULT = 0;
35    private static final int MODE_OVERFLING = 1;
36    private static final int MODE_SPRINGBACK = 2;
37
38    private Scroller mDefaultScroller;
39    private Scroller mDecelScroller;
40    private Scroller mAccelDecelScroller;
41    private Scroller mCurrScroller;
42
43    private int mScrollMode = MODE_DEFAULT;
44
45    private int mMinimumX;
46    private int mMinimumY;
47    private int mMaximumX;
48    private int mMaximumY;
49
50    public OverScroller(Context context) {
51        mDefaultScroller = new Scroller(context);
52        mDecelScroller = new Scroller(context, new DecelerateInterpolator());
53        mAccelDecelScroller = new Scroller(context, new AccelerateDecelerateInterpolator());
54        mCurrScroller = mDefaultScroller;
55    }
56
57    /**
58     * Call this when you want to know the new location.  If it returns true,
59     * the animation is not yet finished.  loc will be altered to provide the
60     * new location.
61     */
62    public boolean computeScrollOffset() {
63        boolean inProgress = mCurrScroller.computeScrollOffset();
64
65        switch (mScrollMode) {
66        case MODE_OVERFLING:
67            if (!inProgress) {
68                // Overfling ended
69                if (springback(mCurrScroller.getCurrX(), mCurrScroller.getCurrY(),
70                        mMinimumX, mMaximumX, mMinimumY, mMaximumY, mAccelDecelScroller)) {
71                    return mCurrScroller.computeScrollOffset();
72                } else {
73                    mCurrScroller = mDefaultScroller;
74                    mScrollMode = MODE_DEFAULT;
75                }
76            }
77            break;
78
79        case MODE_SPRINGBACK:
80            if (!inProgress) {
81                mCurrScroller = mDefaultScroller;
82                mScrollMode = MODE_DEFAULT;
83            }
84            break;
85
86        case MODE_DEFAULT:
87            // Fling/autoscroll - did we go off the edge?
88            if (inProgress) {
89                Scroller scroller = mCurrScroller;
90                final int x = scroller.getCurrX();
91                final int y = scroller.getCurrY();
92                final int minX = mMinimumX;
93                final int maxX = mMaximumX;
94                final int minY = mMinimumY;
95                final int maxY = mMaximumY;
96                if (x < minX || x > maxX || y < minY || y > maxY) {
97                    final int startx = scroller.getStartX();
98                    final int starty = scroller.getStartY();
99                    final int time = scroller.timePassed();
100                    final float timeSecs = time / 1000.f;
101                    final float xvel = ((x - startx) / timeSecs);
102                    final float yvel = ((y - starty) / timeSecs);
103
104                    if ((x < minX && xvel > 0) || (y < minY && yvel > 0) ||
105                            (x > maxX && xvel < 0) || (y > maxY && yvel < 0)) {
106                        // If our velocity would take us back into valid areas,
107                        // try to springback rather than overfling.
108                        if (springback(x, y, minX, maxX, minY, maxY)) {
109                            return mCurrScroller.computeScrollOffset();
110                        }
111                    } else {
112                        overfling(x, y, xvel, yvel);
113                        return mCurrScroller.computeScrollOffset();
114                    }
115                }
116            }
117            break;
118        }
119
120        return inProgress;
121    }
122
123    private void overfling(int startx, int starty, float xvel, float yvel) {
124        Scroller scroller = mDecelScroller;
125        final float durationSecs = (OVERFLING_DURATION / 1000.f);
126        int dx = (int)(xvel * durationSecs) / 8;
127        int dy = (int)(yvel * durationSecs) / 8;
128        scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION);
129        mCurrScroller.abortAnimation();
130        mCurrScroller = scroller;
131        mScrollMode = MODE_OVERFLING;
132    }
133
134    /**
135     * Call this when you want to 'spring back' into a valid coordinate range.
136     *
137     * @param startX Starting X coordinate
138     * @param startY Starting Y coordinate
139     * @param minX Minimum valid X value
140     * @param maxX Maximum valid X value
141     * @param minY Minimum valid Y value
142     * @param maxY Minimum valid Y value
143     * @return true if a springback was initiated, false if startX/startY was
144     *          already within the valid range.
145     */
146    public boolean springback(int startX, int startY, int minX, int maxX,
147            int minY, int maxY) {
148        return springback(startX, startY, minX, maxX, minY, maxY, mDecelScroller);
149    }
150
151    private boolean springback(int startX, int startY, int minX, int maxX,
152            int minY, int maxY, Scroller scroller) {
153        int xoff = 0;
154        int yoff = 0;
155        if (startX < minX) {
156            xoff = minX - startX;
157        } else if (startX > maxX) {
158            xoff = maxX - startX;
159        }
160        if (startY < minY) {
161            yoff = minY - startY;
162        } else if (startY > maxY) {
163            yoff = maxY - startY;
164        }
165
166        if (xoff != 0 || yoff != 0) {
167            scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION);
168            mCurrScroller.abortAnimation();
169            mCurrScroller = scroller;
170            mScrollMode = MODE_SPRINGBACK;
171            return true;
172        }
173
174        return false;
175    }
176
177    /**
178     *
179     * Returns whether the scroller has finished scrolling.
180     *
181     * @return True if the scroller has finished scrolling, false otherwise.
182     */
183    public final boolean isFinished() {
184        return mCurrScroller.isFinished();
185    }
186
187    /**
188     * Returns the current X offset in the scroll.
189     *
190     * @return The new X offset as an absolute distance from the origin.
191     */
192    public final int getCurrX() {
193        return mCurrScroller.getCurrX();
194    }
195
196    /**
197     * Returns the current Y offset in the scroll.
198     *
199     * @return The new Y offset as an absolute distance from the origin.
200     */
201    public final int getCurrY() {
202        return mCurrScroller.getCurrY();
203    }
204
205    /**
206     * Stops the animation, resets any springback/overfling and completes
207     * any standard flings/scrolls in progress.
208     */
209    public void abortAnimation() {
210        mCurrScroller.abortAnimation();
211        mCurrScroller = mDefaultScroller;
212        mScrollMode = MODE_DEFAULT;
213        mCurrScroller.abortAnimation();
214    }
215
216    /**
217     * Start scrolling by providing a starting point and the distance to travel.
218     * The scroll will use the default value of 250 milliseconds for the
219     * duration. This version does not spring back to boundaries.
220     *
221     * @param startX Starting horizontal scroll offset in pixels. Positive
222     *        numbers will scroll the content to the left.
223     * @param startY Starting vertical scroll offset in pixels. Positive numbers
224     *        will scroll the content up.
225     * @param dx Horizontal distance to travel. Positive numbers will scroll the
226     *        content to the left.
227     * @param dy Vertical distance to travel. Positive numbers will scroll the
228     *        content up.
229     */
230    public void startScroll(int startX, int startY, int dx, int dy) {
231        final int minX = Math.min(startX, startX + dx);
232        final int maxX = Math.max(startX, startX + dx);
233        final int minY = Math.min(startY, startY + dy);
234        final int maxY = Math.max(startY, startY + dy);
235        startScroll(startX, startY, dx, dy, minX, maxX, minY, maxY);
236    }
237
238    /**
239     * Start scrolling by providing a starting point and the distance to travel.
240     * The scroll will use the default value of 250 milliseconds for the
241     * duration. This version will spring back to the provided boundaries if
242     * the scroll value would take it too far.
243     *
244     * @param startX Starting horizontal scroll offset in pixels. Positive
245     *        numbers will scroll the content to the left.
246     * @param startY Starting vertical scroll offset in pixels. Positive numbers
247     *        will scroll the content up.
248     * @param dx Horizontal distance to travel. Positive numbers will scroll the
249     *        content to the left.
250     * @param dy Vertical distance to travel. Positive numbers will scroll the
251     *        content up.
252     * @param minX Minimum X value. The scroller will not scroll past this
253     *        point.
254     * @param maxX Maximum X value. The scroller will not scroll past this
255     *        point.
256     * @param minY Minimum Y value. The scroller will not scroll past this
257     *        point.
258     * @param maxY Maximum Y value. The scroller will not scroll past this
259     *        point.
260     */
261    public void startScroll(int startX, int startY, int dx, int dy,
262            int minX, int maxX, int minY, int maxY) {
263        mCurrScroller.abortAnimation();
264        mCurrScroller = mDefaultScroller;
265        mScrollMode = MODE_DEFAULT;
266        mMinimumX = minX;
267        mMaximumX = maxX;
268        mMinimumY = minY;
269        mMaximumY = maxY;
270        mCurrScroller.startScroll(startX, startY, dx, dy);
271    }
272
273    /**
274     * Start scrolling by providing a starting point and the distance to travel.
275     *
276     * @param startX Starting horizontal scroll offset in pixels. Positive
277     *        numbers will scroll the content to the left.
278     * @param startY Starting vertical scroll offset in pixels. Positive numbers
279     *        will scroll the content up.
280     * @param dx Horizontal distance to travel. Positive numbers will scroll the
281     *        content to the left.
282     * @param dy Vertical distance to travel. Positive numbers will scroll the
283     *        content up.
284     * @param duration Duration of the scroll in milliseconds.
285     */
286    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
287        mCurrScroller.abortAnimation();
288        mCurrScroller = mDefaultScroller;
289        mScrollMode = MODE_DEFAULT;
290        mMinimumX = Math.min(startX, startX + dx);
291        mMinimumY = Math.min(startY, startY + dy);
292        mMaximumX = Math.max(startX, startX + dx);
293        mMaximumY = Math.max(startY, startY + dy);
294        mCurrScroller.startScroll(startX, startY, dx, dy, duration);
295    }
296
297    /**
298     * Returns the duration of the active scroll in progress; standard, fling,
299     * springback, or overfling. Does not account for any overflings or springback
300     * that may result.
301     */
302    public int getDuration() {
303        return mCurrScroller.getDuration();
304    }
305
306    /**
307     * Start scrolling based on a fling gesture. The distance travelled will
308     * depend on the initial velocity of the fling.
309     *
310     * @param startX Starting point of the scroll (X)
311     * @param startY Starting point of the scroll (Y)
312     * @param velocityX Initial velocity of the fling (X) measured in pixels per
313     *        second.
314     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
315     *        second
316     * @param minX Minimum X value. The scroller will not scroll past this
317     *        point.
318     * @param maxX Maximum X value. The scroller will not scroll past this
319     *        point.
320     * @param minY Minimum Y value. The scroller will not scroll past this
321     *        point.
322     * @param maxY Maximum Y value. The scroller will not scroll past this
323     *        point.
324     */
325    public void fling(int startX, int startY, int velocityX, int velocityY,
326            int minX, int maxX, int minY, int maxY) {
327        this.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
328    }
329
330    /**
331     * Start scrolling based on a fling gesture. The distance travelled will
332     * depend on the initial velocity of the fling.
333     *
334     * @param startX Starting point of the scroll (X)
335     * @param startY Starting point of the scroll (Y)
336     * @param velocityX Initial velocity of the fling (X) measured in pixels per
337     *        second.
338     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
339     *        second
340     * @param minX Minimum X value. The scroller will not scroll past this
341     *        point unless overX > 0. If overfling is allowed, it will use minX
342     *        as a springback boundary.
343     * @param maxX Maximum X value. The scroller will not scroll past this
344     *        point unless overX > 0. If overfling is allowed, it will use maxX
345     *        as a springback boundary.
346     * @param minY Minimum Y value. The scroller will not scroll past this
347     *        point unless overY > 0. If overfling is allowed, it will use minY
348     *        as a springback boundary.
349     * @param maxY Maximum Y value. The scroller will not scroll past this
350     *        point unless overY > 0. If overfling is allowed, it will use maxY
351     *        as a springback boundary.
352     * @param overX Overfling range. If > 0, horizontal overfling in either
353     *        direction will be possible.
354     * @param overY Overfling range. If > 0, vertical overfling in either
355     *        direction will be possible.
356     */
357    public void fling(int startX, int startY, int velocityX, int velocityY,
358            int minX, int maxX, int minY, int maxY, int overX, int overY) {
359        mCurrScroller = mDefaultScroller;
360        mScrollMode = MODE_DEFAULT;
361        mMinimumX = minX;
362        mMaximumX = maxX;
363        mMinimumY = minY;
364        mMaximumY = maxY;
365        mCurrScroller.fling(startX, startY, velocityX, velocityY,
366                minX - overX, maxX + overX, minY - overY, maxY + overY);
367    }
368
369    /**
370     * Returns where the scroll will end. Valid only for "fling" scrolls.
371     *
372     * @return The final X offset as an absolute distance from the origin.
373     */
374    public int getFinalX() {
375        return mCurrScroller.getFinalX();
376    }
377
378    /**
379     * Returns where the scroll will end. Valid only for "fling" scrolls.
380     *
381     * @return The final Y offset as an absolute distance from the origin.
382     */
383    public int getFinalY() {
384        return mCurrScroller.getFinalY();
385    }
386}
387