Scroller.java revision 4bede9e425875542976a422222510fa4056a8339
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.hardware.SensorManager;
21import android.view.ViewConfiguration;
22import android.view.animation.AnimationUtils;
23import android.view.animation.Interpolator;
24
25
26/**
27 * This class encapsulates scrolling.  The duration of the scroll
28 * can be passed in the constructor and specifies the maximum time that
29 * the scrolling animation should take.  Past this time, the scrolling is
30 * automatically moved to its final stage and computeScrollOffset()
31 * will always return false to indicate that scrolling is over.
32 */
33public class Scroller  {
34    private int mMode;
35
36    private int mStartX;
37    private int mStartY;
38    private int mFinalX;
39    private int mFinalY;
40
41    private int mMinX;
42    private int mMaxX;
43    private int mMinY;
44    private int mMaxY;
45
46    private int mCurrX;
47    private int mCurrY;
48    private long mStartTime;
49    private int mDuration;
50    private float mDurationReciprocal;
51    private float mDeltaX;
52    private float mDeltaY;
53    private float mViscousFluidScale;
54    private float mViscousFluidNormalize;
55    private boolean mFinished;
56    private Interpolator mInterpolator;
57
58    private float mCoeffX = 0.0f;
59    private float mCoeffY = 1.0f;
60    private float mVelocity;
61
62    private static final int DEFAULT_DURATION = 250;
63    private static final int SCROLL_MODE = 0;
64    private static final int FLING_MODE = 1;
65
66    private float mDeceleration;
67    private final float mPpi;
68
69    /**
70     * Create a Scroller with the default duration and interpolator.
71     */
72    public Scroller(Context context) {
73        this(context, null);
74    }
75
76    /**
77     * Create a Scroller with the specified interpolator. If the interpolator is
78     * null, the default (viscous) interpolator will be used.
79     */
80    public Scroller(Context context, Interpolator interpolator) {
81        mFinished = true;
82        mInterpolator = interpolator;
83        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
84        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
85    }
86
87    /**
88     * The amount of friction applied to flings. The default value
89     * is {@link ViewConfiguration#getScrollFriction}.
90     *
91     * @return A scalar dimensionless value representing the coefficient of
92     *         friction.
93     */
94    public final void setFriction(float friction) {
95        computeDeceleration(friction);
96    }
97
98    private float computeDeceleration(float friction) {
99        return SensorManager.GRAVITY_EARTH   // g (m/s^2)
100                      * 39.37f               // inch/meter
101                      * mPpi                 // pixels per inch
102                      * friction;
103    }
104
105    /**
106     *
107     * Returns whether the scroller has finished scrolling.
108     *
109     * @return True if the scroller has finished scrolling, false otherwise.
110     */
111    public final boolean isFinished() {
112        return mFinished;
113    }
114
115    /**
116     * Force the finished field to a particular value.
117     *
118     * @param finished The new finished value.
119     */
120    public final void forceFinished(boolean finished) {
121        mFinished = finished;
122    }
123
124    /**
125     * Returns how long the scroll event will take, in milliseconds.
126     *
127     * @return The duration of the scroll in milliseconds.
128     */
129    public final int getDuration() {
130        return mDuration;
131    }
132
133    /**
134     * Returns the current X offset in the scroll.
135     *
136     * @return The new X offset as an absolute distance from the origin.
137     */
138    public final int getCurrX() {
139        return mCurrX;
140    }
141
142    /**
143     * Returns the current Y offset in the scroll.
144     *
145     * @return The new Y offset as an absolute distance from the origin.
146     */
147    public final int getCurrY() {
148        return mCurrY;
149    }
150
151    /**
152     * @hide
153     * Returns the current velocity.
154     *
155     * @return The original velocity less the deceleration. Result may be
156     * negative.
157     */
158    public float getCurrVelocity() {
159        return mVelocity - mDeceleration * timePassed() / 2000.0f;
160    }
161
162    /**
163     * Returns the start X offset in the scroll.
164     *
165     * @return The start X offset as an absolute distance from the origin.
166     */
167    public final int getStartX() {
168        return mStartX;
169    }
170
171    /**
172     * Returns the start Y offset in the scroll.
173     *
174     * @return The start Y offset as an absolute distance from the origin.
175     */
176    public final int getStartY() {
177        return mStartY;
178    }
179
180    /**
181     * Returns where the scroll will end. Valid only for "fling" scrolls.
182     *
183     * @return The final X offset as an absolute distance from the origin.
184     */
185    public final int getFinalX() {
186        return mFinalX;
187    }
188
189    /**
190     * Returns where the scroll will end. Valid only for "fling" scrolls.
191     *
192     * @return The final Y offset as an absolute distance from the origin.
193     */
194    public final int getFinalY() {
195        return mFinalY;
196    }
197
198    /**
199     * Call this when you want to know the new location.  If it returns true,
200     * the animation is not yet finished.  loc will be altered to provide the
201     * new location.
202     */
203    public boolean computeScrollOffset() {
204        if (mFinished) {
205            return false;
206        }
207
208        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
209
210        if (timePassed < mDuration) {
211            switch (mMode) {
212            case SCROLL_MODE:
213                float x = (float)timePassed * mDurationReciprocal;
214
215                if (mInterpolator == null)
216                    x = viscousFluid(x);
217                else
218                    x = mInterpolator.getInterpolation(x);
219
220                mCurrX = mStartX + Math.round(x * mDeltaX);
221                mCurrY = mStartY + Math.round(x * mDeltaY);
222                break;
223            case FLING_MODE:
224                float timePassedSeconds = timePassed / 1000.0f;
225                float distance = (mVelocity * timePassedSeconds)
226                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
227
228                mCurrX = mStartX + Math.round(distance * mCoeffX);
229                // Pin to mMinX <= mCurrX <= mMaxX
230                mCurrX = Math.min(mCurrX, mMaxX);
231                mCurrX = Math.max(mCurrX, mMinX);
232
233                mCurrY = mStartY + Math.round(distance * mCoeffY);
234                // Pin to mMinY <= mCurrY <= mMaxY
235                mCurrY = Math.min(mCurrY, mMaxY);
236                mCurrY = Math.max(mCurrY, mMinY);
237
238                if (mCurrX == mFinalX && mCurrY == mFinalY) {
239                    mFinished = true;
240                }
241
242                break;
243            }
244        }
245        else {
246            mCurrX = mFinalX;
247            mCurrY = mFinalY;
248            mFinished = true;
249        }
250        return true;
251    }
252
253    /**
254     * Start scrolling by providing a starting point and the distance to travel.
255     * The scroll will use the default value of 250 milliseconds for the
256     * duration.
257     *
258     * @param startX Starting horizontal scroll offset in pixels. Positive
259     *        numbers will scroll the content to the left.
260     * @param startY Starting vertical scroll offset in pixels. Positive numbers
261     *        will scroll the content up.
262     * @param dx Horizontal distance to travel. Positive numbers will scroll the
263     *        content to the left.
264     * @param dy Vertical distance to travel. Positive numbers will scroll the
265     *        content up.
266     */
267    public void startScroll(int startX, int startY, int dx, int dy) {
268        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
269    }
270
271    /**
272     * Start scrolling by providing a starting point and the distance to travel.
273     *
274     * @param startX Starting horizontal scroll offset in pixels. Positive
275     *        numbers will scroll the content to the left.
276     * @param startY Starting vertical scroll offset in pixels. Positive numbers
277     *        will scroll the content up.
278     * @param dx Horizontal distance to travel. Positive numbers will scroll the
279     *        content to the left.
280     * @param dy Vertical distance to travel. Positive numbers will scroll the
281     *        content up.
282     * @param duration Duration of the scroll in milliseconds.
283     */
284    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
285        mMode = SCROLL_MODE;
286        mFinished = false;
287        mDuration = duration;
288        mStartTime = AnimationUtils.currentAnimationTimeMillis();
289        mStartX = startX;
290        mStartY = startY;
291        mFinalX = startX + dx;
292        mFinalY = startY + dy;
293        mDeltaX = dx;
294        mDeltaY = dy;
295        mDurationReciprocal = 1.0f / (float) mDuration;
296        // This controls the viscous fluid effect (how much of it)
297        mViscousFluidScale = 8.0f;
298        // must be set to 1.0 (used in viscousFluid())
299        mViscousFluidNormalize = 1.0f;
300        mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
301    }
302
303    /**
304     * Start scrolling based on a fling gesture. The distance travelled will
305     * depend on the initial velocity of the fling.
306     *
307     * @param startX Starting point of the scroll (X)
308     * @param startY Starting point of the scroll (Y)
309     * @param velocityX Initial velocity of the fling (X) measured in pixels per
310     *        second.
311     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
312     *        second
313     * @param minX Minimum X value. The scroller will not scroll past this
314     *        point.
315     * @param maxX Maximum X value. The scroller will not scroll past this
316     *        point.
317     * @param minY Minimum Y value. The scroller will not scroll past this
318     *        point.
319     * @param maxY Maximum Y value. The scroller will not scroll past this
320     *        point.
321     */
322    public void fling(int startX, int startY, int velocityX, int velocityY,
323            int minX, int maxX, int minY, int maxY) {
324        mMode = FLING_MODE;
325        mFinished = false;
326
327        float velocity = (float)Math.hypot(velocityX, velocityY);
328
329        mVelocity = velocity;
330        mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
331                                                            // milliseconds
332        mStartTime = AnimationUtils.currentAnimationTimeMillis();
333        mStartX = startX;
334        mStartY = startY;
335
336        mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity;
337        mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
338
339        int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
340
341        mMinX = minX;
342        mMaxX = maxX;
343        mMinY = minY;
344        mMaxY = maxY;
345
346
347        mFinalX = startX + Math.round(totalDistance * mCoeffX);
348        // Pin to mMinX <= mFinalX <= mMaxX
349        mFinalX = Math.min(mFinalX, mMaxX);
350        mFinalX = Math.max(mFinalX, mMinX);
351
352        mFinalY = startY + Math.round(totalDistance * mCoeffY);
353        // Pin to mMinY <= mFinalY <= mMaxY
354        mFinalY = Math.min(mFinalY, mMaxY);
355        mFinalY = Math.max(mFinalY, mMinY);
356    }
357
358
359
360    private float viscousFluid(float x)
361    {
362        x *= mViscousFluidScale;
363        if (x < 1.0f) {
364            x -= (1.0f - (float)Math.exp(-x));
365        } else {
366            float start = 0.36787944117f;   // 1/e == exp(-1)
367            x = 1.0f - (float)Math.exp(1.0f - x);
368            x = start + x * (1.0f - start);
369        }
370        x *= mViscousFluidNormalize;
371        return x;
372    }
373
374    /**
375     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
376     * aborting the animating cause the scroller to move to the final x and y
377     * position
378     *
379     * @see #forceFinished(boolean)
380     */
381    public void abortAnimation() {
382        mCurrX = mFinalX;
383        mCurrY = mFinalY;
384        mFinished = true;
385    }
386
387    /**
388     * Extend the scroll animation. This allows a running animation to scroll
389     * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
390     *
391     * @param extend Additional time to scroll in milliseconds.
392     * @see #setFinalX(int)
393     * @see #setFinalY(int)
394     */
395    public void extendDuration(int extend) {
396        int passed = timePassed();
397        mDuration = passed + extend;
398        mDurationReciprocal = 1.0f / (float)mDuration;
399        mFinished = false;
400    }
401
402    /**
403     * Returns the time elapsed since the beginning of the scrolling.
404     *
405     * @return The elapsed time in milliseconds.
406     */
407    public int timePassed() {
408        return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
409    }
410
411    /**
412     * Sets the final position (X) for this scroller.
413     *
414     * @param newX The new X offset as an absolute distance from the origin.
415     * @see #extendDuration(int)
416     * @see #setFinalY(int)
417     */
418    public void setFinalX(int newX) {
419        mFinalX = newX;
420        mDeltaX = mFinalX - mStartX;
421        mFinished = false;
422    }
423
424    /**
425     * Sets the final position (Y) for this scroller.
426     *
427     * @param newY The new Y offset as an absolute distance from the origin.
428     * @see #extendDuration(int)
429     * @see #setFinalX(int)
430     */
431    public void setFinalY(int newY) {
432        mFinalY = newY;
433        mDeltaY = mFinalY - mStartY;
434        mFinished = false;
435    }
436}
437