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