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