1/*
2 * Copyright (C) 2012 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.support.v4.widget;
18
19import android.content.Context;
20import android.os.Build;
21import android.view.animation.Interpolator;
22import android.widget.Scroller;
23
24/**
25 * Provides access to new {@link android.widget.Scroller Scroller} APIs when available.
26 *
27 * <p>This class provides a platform version-independent mechanism for obeying the
28 * current device's preferred scroll physics and fling behavior. It offers a subset of
29 * the APIs from Scroller or OverScroller.</p>
30 */
31public class ScrollerCompat {
32    Object mScroller;
33
34    interface ScrollerCompatImpl {
35        Object createScroller(Context context, Interpolator interpolator);
36        boolean isFinished(Object scroller);
37        int getCurrX(Object scroller);
38        int getCurrY(Object scroller);
39        float getCurrVelocity(Object scroller);
40        boolean computeScrollOffset(Object scroller);
41        void startScroll(Object scroller, int startX, int startY, int dx, int dy);
42        void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration);
43        void fling(Object scroller, int startX, int startY, int velX, int velY,
44                int minX, int maxX, int minY, int maxY);
45        void fling(Object scroller, int startX, int startY, int velX, int velY,
46                int minX, int maxX, int minY, int maxY, int overX, int overY);
47        void abortAnimation(Object scroller);
48        void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX);
49        void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY);
50        boolean isOverScrolled(Object scroller);
51        int getFinalX(Object scroller);
52        int getFinalY(Object scroller);
53    }
54
55    static class ScrollerCompatImplBase implements ScrollerCompatImpl {
56        @Override
57        public Object createScroller(Context context, Interpolator interpolator) {
58            return interpolator != null ?
59                    new Scroller(context, interpolator) : new Scroller(context);
60        }
61
62        @Override
63        public boolean isFinished(Object scroller) {
64            return ((Scroller) scroller).isFinished();
65        }
66
67        @Override
68        public int getCurrX(Object scroller) {
69            return ((Scroller) scroller).getCurrX();
70        }
71
72        @Override
73        public int getCurrY(Object scroller) {
74            return ((Scroller) scroller).getCurrY();
75        }
76
77        @Override
78        public float getCurrVelocity(Object scroller) {
79            return 0;
80        }
81
82        @Override
83        public boolean computeScrollOffset(Object scroller) {
84            return ((Scroller) scroller).computeScrollOffset();
85        }
86
87        @Override
88        public void startScroll(Object scroller, int startX, int startY, int dx, int dy) {
89            ((Scroller) scroller).startScroll(startX, startY, dx, dy);
90        }
91
92        @Override
93        public void startScroll(Object scroller, int startX, int startY, int dx, int dy,
94                int duration) {
95            ((Scroller) scroller).startScroll(startX, startY, dx, dy, duration);
96        }
97
98        @Override
99        public void fling(Object scroller, int startX, int startY, int velX, int velY,
100                int minX, int maxX, int minY, int maxY) {
101            ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY);
102        }
103
104        @Override
105        public void fling(Object scroller, int startX, int startY, int velX, int velY,
106                int minX, int maxX, int minY, int maxY, int overX, int overY) {
107            ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY);
108        }
109
110        @Override
111        public void abortAnimation(Object scroller) {
112            ((Scroller) scroller).abortAnimation();
113        }
114
115        @Override
116        public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX,
117                int overX) {
118            // No-op
119        }
120
121        @Override
122        public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) {
123            // No-op
124        }
125
126        @Override
127        public boolean isOverScrolled(Object scroller) {
128            // Always false
129            return false;
130        }
131
132        @Override
133        public int getFinalX(Object scroller) {
134            return ((Scroller) scroller).getFinalX();
135        }
136
137        @Override
138        public int getFinalY(Object scroller) {
139            return ((Scroller) scroller).getFinalY();
140        }
141    }
142
143    static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl {
144        @Override
145        public Object createScroller(Context context, Interpolator interpolator) {
146            return ScrollerCompatGingerbread.createScroller(context, interpolator);
147        }
148
149        @Override
150        public boolean isFinished(Object scroller) {
151            return ScrollerCompatGingerbread.isFinished(scroller);
152        }
153
154        @Override
155        public int getCurrX(Object scroller) {
156            return ScrollerCompatGingerbread.getCurrX(scroller);
157        }
158
159        @Override
160        public int getCurrY(Object scroller) {
161            return ScrollerCompatGingerbread.getCurrY(scroller);
162        }
163
164        @Override
165        public float getCurrVelocity(Object scroller) {
166            return 0;
167        }
168
169        @Override
170        public boolean computeScrollOffset(Object scroller) {
171            return ScrollerCompatGingerbread.computeScrollOffset(scroller);
172        }
173
174        @Override
175        public void startScroll(Object scroller, int startX, int startY, int dx, int dy) {
176            ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy);
177        }
178
179        @Override
180        public void startScroll(Object scroller, int startX, int startY, int dx, int dy,
181                int duration) {
182            ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy, duration);
183        }
184
185        @Override
186        public void fling(Object scroller, int startX, int startY, int velX, int velY,
187                int minX, int maxX, int minY, int maxY) {
188            ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY,
189                    minX, maxX, minY, maxY);
190        }
191
192        @Override
193        public void fling(Object scroller, int startX, int startY, int velX, int velY,
194                int minX, int maxX, int minY, int maxY, int overX, int overY) {
195            ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY,
196                    minX, maxX, minY, maxY, overX, overY);
197        }
198
199        @Override
200        public void abortAnimation(Object scroller) {
201            ScrollerCompatGingerbread.abortAnimation(scroller);
202        }
203
204        @Override
205        public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX,
206                int overX) {
207            ScrollerCompatGingerbread.notifyHorizontalEdgeReached(scroller, startX, finalX, overX);
208        }
209
210        @Override
211        public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) {
212            ScrollerCompatGingerbread.notifyVerticalEdgeReached(scroller, startY, finalY, overY);
213        }
214
215        @Override
216        public boolean isOverScrolled(Object scroller) {
217            return ScrollerCompatGingerbread.isOverScrolled(scroller);
218        }
219
220        @Override
221        public int getFinalX(Object scroller) {
222            return ScrollerCompatGingerbread.getFinalX(scroller);
223        }
224
225        @Override
226        public int getFinalY(Object scroller) {
227            return ScrollerCompatGingerbread.getFinalY(scroller);
228        }
229    }
230
231    static class ScrollerCompatImplIcs extends ScrollerCompatImplGingerbread {
232        @Override
233        public float getCurrVelocity(Object scroller) {
234            return ScrollerCompatIcs.getCurrVelocity(scroller);
235        }
236    }
237
238    static final ScrollerCompatImpl IMPL;
239    static {
240        final int version = Build.VERSION.SDK_INT;
241        if (version >= 14) { // ICS
242            IMPL = new ScrollerCompatImplIcs();
243        } else if (version >= 9) { // Gingerbread
244            IMPL = new ScrollerCompatImplGingerbread();
245        } else {
246            IMPL = new ScrollerCompatImplBase();
247        }
248    }
249
250    public static ScrollerCompat create(Context context) {
251        return create(context, null);
252    }
253
254    public static ScrollerCompat create(Context context, Interpolator interpolator) {
255        return new ScrollerCompat(context, interpolator);
256    }
257
258    ScrollerCompat(Context context, Interpolator interpolator) {
259        mScroller = IMPL.createScroller(context, interpolator);
260    }
261
262    /**
263     * Returns whether the scroller has finished scrolling.
264     *
265     * @return True if the scroller has finished scrolling, false otherwise.
266     */
267    public boolean isFinished() {
268        return IMPL.isFinished(mScroller);
269    }
270
271    /**
272     * Returns the current X offset in the scroll.
273     *
274     * @return The new X offset as an absolute distance from the origin.
275     */
276    public int getCurrX() {
277        return IMPL.getCurrX(mScroller);
278    }
279
280    /**
281     * Returns the current Y offset in the scroll.
282     *
283     * @return The new Y offset as an absolute distance from the origin.
284     */
285    public int getCurrY() {
286        return IMPL.getCurrY(mScroller);
287    }
288
289    /**
290     * @return The final X position for the scroll in progress, if known.
291     */
292    public int getFinalX() {
293        return IMPL.getFinalX(mScroller);
294    }
295
296    /**
297     * @return The final Y position for the scroll in progress, if known.
298     */
299    public int getFinalY() {
300        return IMPL.getFinalY(mScroller);
301    }
302
303    /**
304     * Returns the current velocity on platform versions that support it.
305     *
306     * <p>The device must support at least API level 14 (Ice Cream Sandwich).
307     * On older platform versions this method will return 0. This method should
308     * only be used as input for nonessential visual effects such as {@link EdgeEffectCompat}.</p>
309     *
310     * @return The original velocity less the deceleration. Result may be
311     * negative.
312     */
313    public float getCurrVelocity() {
314        return IMPL.getCurrVelocity(mScroller);
315    }
316
317    /**
318     * Call this when you want to know the new location.  If it returns true,
319     * the animation is not yet finished.  loc will be altered to provide the
320     * new location.
321     */
322    public boolean computeScrollOffset() {
323        return IMPL.computeScrollOffset(mScroller);
324    }
325
326    /**
327     * Start scrolling by providing a starting point and the distance to travel.
328     * The scroll will use the default value of 250 milliseconds for the
329     * duration.
330     *
331     * @param startX Starting horizontal scroll offset in pixels. Positive
332     *        numbers will scroll the content to the left.
333     * @param startY Starting vertical scroll offset in pixels. Positive numbers
334     *        will scroll the content up.
335     * @param dx Horizontal distance to travel. Positive numbers will scroll the
336     *        content to the left.
337     * @param dy Vertical distance to travel. Positive numbers will scroll the
338     *        content up.
339     */
340    public void startScroll(int startX, int startY, int dx, int dy) {
341        IMPL.startScroll(mScroller, startX, startY, dx, dy);
342    }
343
344    /**
345     * Start scrolling by providing a starting point and the distance to travel.
346     *
347     * @param startX Starting horizontal scroll offset in pixels. Positive
348     *        numbers will scroll the content to the left.
349     * @param startY Starting vertical scroll offset in pixels. Positive numbers
350     *        will scroll the content up.
351     * @param dx Horizontal distance to travel. Positive numbers will scroll the
352     *        content to the left.
353     * @param dy Vertical distance to travel. Positive numbers will scroll the
354     *        content up.
355     * @param duration Duration of the scroll in milliseconds.
356     */
357    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
358        IMPL.startScroll(mScroller, startX, startY, dx, dy, duration);
359    }
360
361    /**
362     * Start scrolling based on a fling gesture. The distance travelled will
363     * depend on the initial velocity of the fling.
364     *
365     * @param startX Starting point of the scroll (X)
366     * @param startY Starting point of the scroll (Y)
367     * @param velocityX Initial velocity of the fling (X) measured in pixels per
368     *        second.
369     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
370     *        second
371     * @param minX Minimum X value. The scroller will not scroll past this
372     *        point.
373     * @param maxX Maximum X value. The scroller will not scroll past this
374     *        point.
375     * @param minY Minimum Y value. The scroller will not scroll past this
376     *        point.
377     * @param maxY Maximum Y value. The scroller will not scroll past this
378     *        point.
379     */
380    public void fling(int startX, int startY, int velocityX, int velocityY,
381            int minX, int maxX, int minY, int maxY) {
382        IMPL.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY);
383    }
384
385    /**
386     * Start scrolling based on a fling gesture. The distance travelled will
387     * depend on the initial velocity of the fling.
388     *
389     * @param startX Starting point of the scroll (X)
390     * @param startY Starting point of the scroll (Y)
391     * @param velocityX Initial velocity of the fling (X) measured in pixels per
392     *        second.
393     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
394     *        second
395     * @param minX Minimum X value. The scroller will not scroll past this
396     *        point.
397     * @param maxX Maximum X value. The scroller will not scroll past this
398     *        point.
399     * @param minY Minimum Y value. The scroller will not scroll past this
400     *        point.
401     * @param maxY Maximum Y value. The scroller will not scroll past this
402     *        point.
403     * @param overX Overfling range. If > 0, horizontal overfling in either
404     *            direction will be possible.
405     * @param overY Overfling range. If > 0, vertical overfling in either
406     *            direction will be possible.
407     */
408    public void fling(int startX, int startY, int velocityX, int velocityY,
409            int minX, int maxX, int minY, int maxY, int overX, int overY) {
410        IMPL.fling(mScroller, startX, startY, velocityX, velocityY,
411                minX, maxX, minY, maxY, overX, overY);
412    }
413
414    /**
415     * Stops the animation. Aborting the animation causes the scroller to move to the final x and y
416     * position.
417     */
418    public void abortAnimation() {
419        IMPL.abortAnimation(mScroller);
420    }
421
422
423    /**
424     * Notify the scroller that we've reached a horizontal boundary.
425     * Normally the information to handle this will already be known
426     * when the animation is started, such as in a call to one of the
427     * fling functions. However there are cases where this cannot be known
428     * in advance. This function will transition the current motion and
429     * animate from startX to finalX as appropriate.
430     *
431     * @param startX Starting/current X position
432     * @param finalX Desired final X position
433     * @param overX Magnitude of overscroll allowed. This should be the maximum
434     *              desired distance from finalX. Absolute value - must be positive.
435     */
436    public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
437        IMPL.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX);
438    }
439
440    /**
441     * Notify the scroller that we've reached a vertical boundary.
442     * Normally the information to handle this will already be known
443     * when the animation is started, such as in a call to one of the
444     * fling functions. However there are cases where this cannot be known
445     * in advance. This function will animate a parabolic motion from
446     * startY to finalY.
447     *
448     * @param startY Starting/current Y position
449     * @param finalY Desired final Y position
450     * @param overY Magnitude of overscroll allowed. This should be the maximum
451     *              desired distance from finalY. Absolute value - must be positive.
452     */
453    public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
454        IMPL.notifyVerticalEdgeReached(mScroller, startY, finalY, overY);
455    }
456
457    /**
458     * Returns whether the current Scroller is currently returning to a valid position.
459     * Valid bounds were provided by the
460     * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
461     *
462     * One should check this value before calling
463     * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
464     * to restore a valid position will then be stopped. The caller has to take into account
465     * the fact that the started scroll will start from an overscrolled position.
466     *
467     * @return true when the current position is overscrolled and in the process of
468     *         interpolating back to a valid value.
469     */
470    public boolean isOverScrolled() {
471        return IMPL.isOverScrolled(mScroller);
472    }
473}
474