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