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