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