OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / core / java / android / widget / OverScroller.java
1 /*
2  * Copyright (C) 2010 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
17 package android.widget;
18
19 import android.content.Context;
20 import android.hardware.SensorManager;
21 import android.util.FloatMath;
22 import android.view.ViewConfiguration;
23 import android.view.animation.AnimationUtils;
24 import android.view.animation.Interpolator;
25
26 /**
27  * This class encapsulates scrolling with the ability to overshoot the bounds
28  * of a scrolling operation. This class is a drop-in replacement for
29  * {@link android.widget.Scroller} in most cases.
30  */
31 public class OverScroller {
32     private int mMode;
33
34     private MagneticOverScroller mScrollerX;
35     private MagneticOverScroller mScrollerY;
36
37     private final Interpolator mInterpolator;
38
39     private static final int DEFAULT_DURATION = 250;
40     private static final int SCROLL_MODE = 0;
41     private static final int FLING_MODE = 1;
42
43     /**
44      * Creates an OverScroller with a viscous fluid scroll interpolator.
45      * @param context
46      */
47     public OverScroller(Context context) {
48         this(context, null);
49     }
50
51     /**
52      * Creates an OverScroller with default edge bounce coefficients.
53      * @param context The context of this application.
54      * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
55      * be used.
56      */
57     public OverScroller(Context context, Interpolator interpolator) {
58         this(context, interpolator, MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
59                 MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
60     }
61
62     /**
63      * Creates an OverScroller.
64      * @param context The context of this application.
65      * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
66      * be used.
67      * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
68      * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
69      * means no bounce.
70      * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
71      */
72     public OverScroller(Context context, Interpolator interpolator,
73             float bounceCoefficientX, float bounceCoefficientY) {
74         mInterpolator = interpolator;
75         mScrollerX = new MagneticOverScroller();
76         mScrollerY = new MagneticOverScroller();
77         MagneticOverScroller.initializeFromContext(context);
78
79         mScrollerX.setBounceCoefficient(bounceCoefficientX);
80         mScrollerY.setBounceCoefficient(bounceCoefficientY);
81     }
82
83     /**
84      *
85      * Returns whether the scroller has finished scrolling.
86      *
87      * @return True if the scroller has finished scrolling, false otherwise.
88      */
89     public final boolean isFinished() {
90         return mScrollerX.mFinished && mScrollerY.mFinished;
91     }
92
93     /**
94      * Force the finished field to a particular value. Contrary to
95      * {@link #abortAnimation()}, forcing the animation to finished
96      * does NOT cause the scroller to move to the final x and y
97      * position.
98      *
99      * @param finished The new finished value.
100      */
101     public final void forceFinished(boolean finished) {
102         mScrollerX.mFinished = mScrollerY.mFinished = finished;
103     }
104
105     /**
106      * Returns the current X offset in the scroll.
107      *
108      * @return The new X offset as an absolute distance from the origin.
109      */
110     public final int getCurrX() {
111         return mScrollerX.mCurrentPosition;
112     }
113
114     /**
115      * Returns the current Y offset in the scroll.
116      *
117      * @return The new Y offset as an absolute distance from the origin.
118      */
119     public final int getCurrY() {
120         return mScrollerY.mCurrentPosition;
121     }
122
123     /**
124      * @hide
125      * Returns the current velocity.
126      *
127      * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
128      */
129     public float getCurrVelocity() {
130         float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
131         squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
132         return FloatMath.sqrt(squaredNorm);
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 mScrollerX.mStart;
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 mScrollerY.mStart;
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 mScrollerX.mFinal;
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 mScrollerY.mFinal;
169     }
170
171     /**
172      * Returns how long the scroll event will take, in milliseconds.
173      *
174      * @return The duration of the scroll in milliseconds.
175      *
176      * @hide Pending removal once nothing depends on it
177      * @deprecated OverScrollers don't necessarily have a fixed duration.
178      *             This function will lie to the best of its ability.
179      */
180     public final int getDuration() {
181         return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
182     }
183
184     /**
185      * Extend the scroll animation. This allows a running animation to scroll
186      * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
187      *
188      * @param extend Additional time to scroll in milliseconds.
189      * @see #setFinalX(int)
190      * @see #setFinalY(int)
191      *
192      * @hide Pending removal once nothing depends on it
193      * @deprecated OverScrollers don't necessarily have a fixed duration.
194      *             Instead of setting a new final position and extending
195      *             the duration of an existing scroll, use startScroll
196      *             to begin a new animation.
197      */
198     public void extendDuration(int extend) {
199         mScrollerX.extendDuration(extend);
200         mScrollerY.extendDuration(extend);
201     }
202
203     /**
204      * Sets the final position (X) for this scroller.
205      *
206      * @param newX The new X offset as an absolute distance from the origin.
207      * @see #extendDuration(int)
208      * @see #setFinalY(int)
209      *
210      * @hide Pending removal once nothing depends on it
211      * @deprecated OverScroller's final position may change during an animation.
212      *             Instead of setting a new final position and extending
213      *             the duration of an existing scroll, use startScroll
214      *             to begin a new animation.
215      */
216     public void setFinalX(int newX) {
217         mScrollerX.setFinalPosition(newX);
218     }
219
220     /**
221      * Sets the final position (Y) for this scroller.
222      *
223      * @param newY The new Y offset as an absolute distance from the origin.
224      * @see #extendDuration(int)
225      * @see #setFinalX(int)
226      *
227      * @hide Pending removal once nothing depends on it
228      * @deprecated OverScroller's final position may change during an animation.
229      *             Instead of setting a new final position and extending
230      *             the duration of an existing scroll, use startScroll
231      *             to begin a new animation.
232      */
233     public void setFinalY(int newY) {
234         mScrollerY.setFinalPosition(newY);
235     }
236
237     /**
238      * Call this when you want to know the new location. If it returns true, the
239      * animation is not yet finished.
240      */
241     public boolean computeScrollOffset() {
242         if (isFinished()) {
243             return false;
244         }
245
246         switch (mMode) {
247             case SCROLL_MODE:
248                 long time = AnimationUtils.currentAnimationTimeMillis();
249                 // Any scroller can be used for time, since they were started
250                 // together in scroll mode. We use X here.
251                 final long elapsedTime = time - mScrollerX.mStartTime;
252
253                 final int duration = mScrollerX.mDuration;
254                 if (elapsedTime < duration) {
255                     float q = (float) (elapsedTime) / duration;
256
257                     if (mInterpolator == null)
258                         q = Scroller.viscousFluid(q);
259                     else
260                         q = mInterpolator.getInterpolation(q);
261
262                     mScrollerX.updateScroll(q);
263                     mScrollerY.updateScroll(q);
264                 } else {
265                     abortAnimation();
266                 }
267                 break;
268
269             case FLING_MODE:
270                 if (!mScrollerX.mFinished) {
271                     if (!mScrollerX.update()) {
272                         if (!mScrollerX.continueWhenFinished()) {
273                             mScrollerX.finish();
274                         }
275                     }
276                 }
277
278                 if (!mScrollerY.mFinished) {
279                     if (!mScrollerY.update()) {
280                         if (!mScrollerY.continueWhenFinished()) {
281                             mScrollerY.finish();
282                         }
283                     }
284                 }
285
286                 break;
287         }
288
289         return true;
290     }
291
292     /**
293      * Start scrolling by providing a starting point and the distance to travel.
294      * The scroll will use the default value of 250 milliseconds for the
295      * duration.
296      *
297      * @param startX Starting horizontal scroll offset in pixels. Positive
298      *        numbers will scroll the content to the left.
299      * @param startY Starting vertical scroll offset in pixels. Positive numbers
300      *        will scroll the content up.
301      * @param dx Horizontal distance to travel. Positive numbers will scroll the
302      *        content to the left.
303      * @param dy Vertical distance to travel. Positive numbers will scroll the
304      *        content up.
305      */
306     public void startScroll(int startX, int startY, int dx, int dy) {
307         startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
308     }
309
310     /**
311      * Start scrolling by providing a starting point and the distance to travel.
312      *
313      * @param startX Starting horizontal scroll offset in pixels. Positive
314      *        numbers will scroll the content to the left.
315      * @param startY Starting vertical scroll offset in pixels. Positive numbers
316      *        will scroll the content up.
317      * @param dx Horizontal distance to travel. Positive numbers will scroll the
318      *        content to the left.
319      * @param dy Vertical distance to travel. Positive numbers will scroll the
320      *        content up.
321      * @param duration Duration of the scroll in milliseconds.
322      */
323     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
324         mMode = SCROLL_MODE;
325         mScrollerX.startScroll(startX, dx, duration);
326         mScrollerY.startScroll(startY, dy, duration);
327     }
328
329     /**
330      * Call this when you want to 'spring back' into a valid coordinate range.
331      *
332      * @param startX Starting X coordinate
333      * @param startY Starting Y coordinate
334      * @param minX Minimum valid X value
335      * @param maxX Maximum valid X value
336      * @param minY Minimum valid Y value
337      * @param maxY Minimum valid Y value
338      * @return true if a springback was initiated, false if startX and startY were
339      *          already within the valid range.
340      */
341     public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
342         mMode = FLING_MODE;
343
344         // Make sure both methods are called.
345         final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
346         final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
347         return spingbackX || spingbackY;
348     }
349
350     public void fling(int startX, int startY, int velocityX, int velocityY,
351             int minX, int maxX, int minY, int maxY) {
352         fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
353     }
354
355     /**
356      * Start scrolling based on a fling gesture. The distance traveled will
357      * depend on the initial velocity of the fling.
358      *
359      * @param startX Starting point of the scroll (X)
360      * @param startY Starting point of the scroll (Y)
361      * @param velocityX Initial velocity of the fling (X) measured in pixels per
362      *            second.
363      * @param velocityY Initial velocity of the fling (Y) measured in pixels per
364      *            second
365      * @param minX Minimum X value. The scroller will not scroll past this point
366      *            unless overX > 0. If overfling is allowed, it will use minX as
367      *            a springback boundary.
368      * @param maxX Maximum X value. The scroller will not scroll past this point
369      *            unless overX > 0. If overfling is allowed, it will use maxX as
370      *            a springback boundary.
371      * @param minY Minimum Y value. The scroller will not scroll past this point
372      *            unless overY > 0. If overfling is allowed, it will use minY as
373      *            a springback boundary.
374      * @param maxY Maximum Y value. The scroller will not scroll past this point
375      *            unless overY > 0. If overfling is allowed, it will use maxY as
376      *            a springback boundary.
377      * @param overX Overfling range. If > 0, horizontal overfling in either
378      *            direction will be possible.
379      * @param overY Overfling range. If > 0, vertical overfling in either
380      *            direction will be possible.
381      */
382     public void fling(int startX, int startY, int velocityX, int velocityY,
383             int minX, int maxX, int minY, int maxY, int overX, int overY) {
384         mMode = FLING_MODE;
385         mScrollerX.fling(startX, velocityX, minX, maxX, overX);
386         mScrollerY.fling(startY, velocityY, minY, maxY, overY);
387     }
388
389     /**
390      * Notify the scroller that we've reached a horizontal boundary.
391      * Normally the information to handle this will already be known
392      * when the animation is started, such as in a call to one of the
393      * fling functions. However there are cases where this cannot be known
394      * in advance. This function will transition the current motion and
395      * animate from startX to finalX as appropriate.
396      *
397      * @param startX Starting/current X position
398      * @param finalX Desired final X position
399      * @param overX Magnitude of overscroll allowed. This should be the maximum
400      *              desired distance from finalX. Absolute value - must be positive.
401      */
402     public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
403         mScrollerX.notifyEdgeReached(startX, finalX, overX);
404     }
405
406     /**
407      * Notify the scroller that we've reached a vertical boundary.
408      * Normally the information to handle this will already be known
409      * when the animation is started, such as in a call to one of the
410      * fling functions. However there are cases where this cannot be known
411      * in advance. This function will animate a parabolic motion from
412      * startY to finalY.
413      *
414      * @param startY Starting/current Y position
415      * @param finalY Desired final Y position
416      * @param overY Magnitude of overscroll allowed. This should be the maximum
417      *              desired distance from finalY.
418      */
419     public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
420         mScrollerY.notifyEdgeReached(startY, finalY, overY);
421     }
422
423     /**
424      * Returns whether the current Scroller is currently returning to a valid position.
425      * Valid bounds were provided by the
426      * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
427      *
428      * One should check this value before calling
429      * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
430      * to restore a valid position will then be stopped. The caller has to take into account
431      * the fact that the started scroll will start from an overscrolled position.
432      *
433      * @return true when the current position is overscrolled and in the process of
434      *         interpolating back to a valid value.
435      */
436     public boolean isOverScrolled() {
437         return ((!mScrollerX.mFinished &&
438                 mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
439                 (!mScrollerY.mFinished &&
440                         mScrollerY.mState != MagneticOverScroller.TO_EDGE));
441     }
442
443     /**
444      * Stops the animation. Contrary to {@link #forceFinished(boolean)},
445      * aborting the animating causes the scroller to move to the final x and y
446      * positions.
447      *
448      * @see #forceFinished(boolean)
449      */
450     public void abortAnimation() {
451         mScrollerX.finish();
452         mScrollerY.finish();
453     }
454
455     /**
456      * Returns the time elapsed since the beginning of the scrolling.
457      *
458      * @return The elapsed time in milliseconds.
459      *
460      * @hide
461      */
462     public int timePassed() {
463         final long time = AnimationUtils.currentAnimationTimeMillis();
464         final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
465         return (int) (time - startTime);
466     }
467
468     static class MagneticOverScroller {
469         // Initial position
470         int mStart;
471
472         // Current position
473         int mCurrentPosition;
474
475         // Final position
476         int mFinal;
477
478         // Initial velocity
479         int mVelocity;
480
481         // Current velocity
482         float mCurrVelocity;
483
484         // Constant current deceleration
485         float mDeceleration;
486
487         // Animation starting time, in system milliseconds
488         long mStartTime;
489
490         // Animation duration, in milliseconds
491         int mDuration;
492
493         // Whether the animation is currently in progress
494         boolean mFinished;
495
496         // Constant gravity value, used to scale deceleration
497         static float GRAVITY;
498
499         static void initializeFromContext(Context context) {
500             final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
501             GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
502                     * 39.37f // inch/meter
503                     * ppi // pixels per inch
504                     * ViewConfiguration.getScrollFriction();
505         }
506
507         private static final int TO_EDGE = 0;
508         private static final int TO_BOUNDARY = 1;
509         private static final int TO_BOUNCE = 2;
510
511         private int mState = TO_EDGE;
512
513         // The allowed overshot distance before boundary is reached.
514         private int mOver;
515
516         // Duration in milliseconds to go back from edge to edge. Springback is half of it.
517         private static final int OVERSCROLL_SPRINGBACK_DURATION = 200;
518
519         // Oscillation period
520         private static final float TIME_COEF =
521             1000.0f * (float) Math.PI / OVERSCROLL_SPRINGBACK_DURATION;
522
523         // If the velocity is smaller than this value, no bounce is triggered
524         // when the edge limits are reached (would result in a zero pixels
525         // displacement anyway).
526         private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f;
527
528         // Proportion of the velocity that is preserved when the edge is reached.
529         private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;
530
531         private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
532
533         MagneticOverScroller() {
534             mFinished = true;
535         }
536
537         void updateScroll(float q) {
538             mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
539         }
540
541         /*
542          * Get a signed deceleration that will reduce the velocity.
543          */
544         static float getDeceleration(int velocity) {
545             return velocity > 0 ? -GRAVITY : GRAVITY;
546         }
547
548         /*
549          * Returns the time (in milliseconds) it will take to go from start to end.
550          */
551         static int computeDuration(int start, int end, float initialVelocity, float deceleration) {
552             final int distance = start - end;
553             final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration
554                     * distance;
555             if (discriminant >= 0.0f) {
556                 float delta = (float) Math.sqrt(discriminant);
557                 if (deceleration < 0.0f) {
558                     delta = -delta;
559                 }
560                 return (int) (1000.0f * (-initialVelocity - delta) / deceleration);
561             }
562
563             // End position can not be reached
564             return 0;
565         }
566
567         void startScroll(int start, int distance, int duration) {
568             mFinished = false;
569
570             mStart = start;
571             mFinal = start + distance;
572
573             mStartTime = AnimationUtils.currentAnimationTimeMillis();
574             mDuration = duration;
575
576             // Unused
577             mDeceleration = 0.0f;
578             mVelocity = 0;
579         }
580
581         void fling(int start, int velocity, int min, int max) {
582             mFinished = false;
583
584             mStart = start;
585             mStartTime = AnimationUtils.currentAnimationTimeMillis();
586
587             mVelocity = velocity;
588
589             mDeceleration = getDeceleration(velocity);
590
591             // A start from an invalid position immediately brings back to a valid position
592             if (mStart < min) {
593                 mDuration = 0;
594                 mFinal = min;
595                 return;
596             }
597
598             if (mStart > max) {
599                 mDuration = 0;
600                 mFinal = max;
601                 return;
602             }
603
604             // Duration are expressed in milliseconds
605             mDuration = (int) (-1000.0f * velocity / mDeceleration);
606
607             mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
608
609             // Clamp to a valid final position
610             if (mFinal < min) {
611                 mFinal = min;
612                 mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
613             }
614
615             if (mFinal > max) {
616                 mFinal = max;
617                 mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
618             }
619         }
620
621         void finish() {
622             mCurrentPosition = mFinal;
623             // Not reset since WebView relies on this value for fast fling.
624             // mCurrVelocity = 0.0f;
625             mFinished = true;
626         }
627
628         void setFinalPosition(int position) {
629             mFinal = position;
630             mFinished = false;
631         }
632
633         void extendDuration(int extend) {
634             final long time = AnimationUtils.currentAnimationTimeMillis();
635             final int elapsedTime = (int) (time - mStartTime);
636             mDuration = elapsedTime + extend;
637             mFinished = false;
638         }
639
640         void setBounceCoefficient(float coefficient) {
641             mBounceCoefficient = coefficient;
642         }
643
644         boolean springback(int start, int min, int max) {
645             mFinished = true;
646
647             mStart = start;
648             mVelocity = 0;
649
650             mStartTime = AnimationUtils.currentAnimationTimeMillis();
651             mDuration = 0;
652
653             if (start < min) {
654                 startSpringback(start, min, false);
655             } else if (start > max) {
656                 startSpringback(start, max, true);
657             }
658
659             return !mFinished;
660         }
661
662         private void startSpringback(int start, int end, boolean positive) {
663             mFinished = false;
664             mState = TO_BOUNCE;
665             mStart = mFinal = end;
666             mDuration = OVERSCROLL_SPRINGBACK_DURATION;
667             mStartTime -= OVERSCROLL_SPRINGBACK_DURATION / 2;
668             mVelocity = (int) (Math.abs(end - start) * TIME_COEF * (positive ? 1.0 : -1.0f));
669         }
670
671         void fling(int start, int velocity, int min, int max, int over) {
672             mState = TO_EDGE;
673             mOver = over;
674
675             mFinished = false;
676
677             mStart = start;
678             mStartTime = AnimationUtils.currentAnimationTimeMillis();
679
680             mVelocity = velocity;
681
682             mDeceleration = getDeceleration(velocity);
683
684             // Duration are expressed in milliseconds
685             mDuration = (int) (-1000.0f * velocity / mDeceleration);
686
687             mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
688
689             // Clamp to a valid final position
690             if (mFinal < min) {
691                 mFinal = min;
692                 mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
693             }
694
695             if (mFinal > max) {
696                 mFinal = max;
697                 mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
698             }
699
700             if (start > max) {
701                 if (start >= max + over) {
702                     springback(max + over, min, max);
703                 } else {
704                     if (velocity <= 0) {
705                         springback(start, min, max);
706                     } else {
707                         long time = AnimationUtils.currentAnimationTimeMillis();
708                         final double durationSinceEdge =
709                             Math.atan((start-max) * TIME_COEF / velocity) / TIME_COEF;
710                         mStartTime = (int) (time - 1000.0f * durationSinceEdge);
711
712                         // Simulate a bounce that started from edge
713                         mStart = max;
714
715                         mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
716
717                         onEdgeReached();
718                     }
719                 }
720             } else {
721                 if (start < min) {
722                     if (start <= min - over) {
723                         springback(min - over, min, max);
724                     } else {
725                         if (velocity >= 0) {
726                             springback(start, min, max);
727                         } else {
728                             long time = AnimationUtils.currentAnimationTimeMillis();
729                             final double durationSinceEdge =
730                                 Math.atan((start-min) * TIME_COEF / velocity) / TIME_COEF;
731                             mStartTime = (int) (time - 1000.0f * durationSinceEdge);
732
733                             // Simulate a bounce that started from edge
734                             mStart = min;
735
736                             mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
737
738                             onEdgeReached();
739                         }
740
741                     }
742                 }
743             }
744         }
745
746         void notifyEdgeReached(int start, int end, int over) {
747             mDeceleration = getDeceleration(mVelocity);
748
749             // Local time, used to compute edge crossing time.
750             float timeCurrent = mCurrVelocity / mDeceleration;
751             final int distance = end - start;
752             float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration)
753                     + (timeCurrent * timeCurrent));
754
755             mVelocity = (int) (mDeceleration * timeEdge);
756
757             // Simulate a symmetric bounce that started from edge
758             mStart = end;
759
760             mOver = over;
761
762             long time = AnimationUtils.currentAnimationTimeMillis();
763             mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge));
764
765             onEdgeReached();
766         }
767
768         private void onEdgeReached() {
769             // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
770             final float distance = mVelocity / TIME_COEF;
771
772             if (Math.abs(distance) < mOver) {
773                 // Spring force will bring us back to final position
774                 mState = TO_BOUNCE;
775                 mFinal = mStart;
776                 mDuration = OVERSCROLL_SPRINGBACK_DURATION;
777             } else {
778                 // Velocity is too high, we will hit the boundary limit
779                 mState = TO_BOUNDARY;
780                 int over = mVelocity > 0 ? mOver : -mOver;
781                 mFinal = mStart + over;
782                 mDuration = (int) (1000.0f * Math.asin(over / distance) / TIME_COEF);
783             }
784         }
785
786         boolean continueWhenFinished() {
787             switch (mState) {
788                 case TO_EDGE:
789                     // Duration from start to null velocity
790                     int duration = (int) (-1000.0f * mVelocity / mDeceleration);
791                     if (mDuration < duration) {
792                         // If the animation was clamped, we reached the edge
793                         mStart = mFinal;
794                         // Speed when edge was reached
795                         mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f);
796                         mStartTime += mDuration;
797                         onEdgeReached();
798                     } else {
799                         // Normal stop, no need to continue
800                         return false;
801                     }
802                     break;
803                 case TO_BOUNDARY:
804                     mStartTime += mDuration;
805                     startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), mVelocity > 0);
806                     break;
807                 case TO_BOUNCE:
808                     //mVelocity = (int) (mVelocity * BOUNCE_COEFFICIENT);
809                     mVelocity = (int) (mVelocity * mBounceCoefficient);
810                     if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
811                         return false;
812                     }
813                     mStartTime += mDuration;
814                     break;
815             }
816
817             update();
818             return true;
819         }
820
821         /*
822          * Update the current position and velocity for current time. Returns
823          * true if update has been done and false if animation duration has been
824          * reached.
825          */
826         boolean update() {
827             final long time = AnimationUtils.currentAnimationTimeMillis();
828             final long duration = time - mStartTime;
829
830             if (duration > mDuration) {
831                 return false;
832             }
833
834             double distance;
835             final float t = duration / 1000.0f;
836             if (mState == TO_EDGE) {
837                 mCurrVelocity = mVelocity + mDeceleration * t;
838                 distance = mVelocity * t + mDeceleration * t * t / 2.0f;
839             } else {
840                 final float d = t * TIME_COEF;
841                 mCurrVelocity = mVelocity * (float)Math.cos(d);
842                 distance = mVelocity / TIME_COEF * Math.sin(d);
843             }
844
845             mCurrentPosition = mStart + (int) distance;
846             return true;
847         }
848     }
849 }