• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.launcher3;
18 
19 import android.animation.TimeInterpolator;
20 import android.content.Context;
21 import android.hardware.SensorManager;
22 import android.os.Build;
23 import android.view.ViewConfiguration;
24 import android.view.animation.AnimationUtils;
25 import android.view.animation.Interpolator;
26 
27 /**
28  * This class differs from the framework {@link android.widget.Scroller} in that
29  * you can modify the Interpolator post-construction.
30  */
31 public class LauncherScroller  {
32     private int mMode;
33 
34     private int mStartX;
35     private int mStartY;
36     private int mFinalX;
37     private int mFinalY;
38 
39     private int mMinX;
40     private int mMaxX;
41     private int mMinY;
42     private int mMaxY;
43 
44     private int mCurrX;
45     private int mCurrY;
46     private long mStartTime;
47     private int mDuration;
48     private float mDurationReciprocal;
49     private float mDeltaX;
50     private float mDeltaY;
51     private boolean mFinished;
52     private TimeInterpolator mInterpolator;
53     private boolean mFlywheel;
54 
55     private float mVelocity;
56     private float mCurrVelocity;
57     private int mDistance;
58 
59     private float mFlingFriction = ViewConfiguration.getScrollFriction();
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 static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
66     private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
67     private static final float START_TENSION = 0.5f;
68     private static final float END_TENSION = 1.0f;
69     private static final float P1 = START_TENSION * INFLEXION;
70     private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
71 
72     private static final int NB_SAMPLES = 100;
73     private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
74     private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
75 
76     private float mDeceleration;
77     private final float mPpi;
78 
79     // A context-specific coefficient adjusted to physical values.
80     private float mPhysicalCoeff;
81 
82     static {
83         float x_min = 0.0f;
84         float y_min = 0.0f;
85         for (int i = 0; i < NB_SAMPLES; i++) {
86             final float alpha = (float) i / NB_SAMPLES;
87 
88             float x_max = 1.0f;
89             float x, tx, coef;
90             while (true) {
91                 x = x_min + (x_max - x_min) / 2.0f;
92                 coef = 3.0f * x * (1.0f - x);
93                 tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
94                 if (Math.abs(tx - alpha) < 1E-5) break;
95                 if (tx > alpha) x_max = x;
96                 else x_min = x;
97             }
98             SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
99 
100             float y_max = 1.0f;
101             float y, dy;
102             while (true) {
103                 y = y_min + (y_max - y_min) / 2.0f;
104                 coef = 3.0f * y * (1.0f - y);
105                 dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
106                 if (Math.abs(dy - alpha) < 1E-5) break;
107                 if (dy > alpha) y_max = y;
108                 else y_min = y;
109             }
110             SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
111         }
112         SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
113 
114         // This controls the viscous fluid effect (how much of it)
115         sViscousFluidScale = 8.0f;
116         // must be set to 1.0 (used in viscousFluid())
117         sViscousFluidNormalize = 1.0f;
118         sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
119 
120     }
121 
122     private static float sViscousFluidScale;
123     private static float sViscousFluidNormalize;
124 
setInterpolator(TimeInterpolator interpolator)125     public void setInterpolator(TimeInterpolator interpolator) {
126         mInterpolator = interpolator;
127     }
128 
129     /**
130      * Create a Scroller with the default duration and interpolator.
131      */
LauncherScroller(Context context)132     public LauncherScroller(Context context) {
133         this(context, null);
134     }
135 
136     /**
137      * Create a Scroller with the specified interpolator. If the interpolator is
138      * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
139      * be in effect for apps targeting Honeycomb or newer.
140      */
LauncherScroller(Context context, Interpolator interpolator)141     public LauncherScroller(Context context, Interpolator interpolator) {
142         this(context, interpolator,
143                 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
144     }
145 
146     /**
147      * Create a Scroller with the specified interpolator. If the interpolator is
148      * null, the default (viscous) interpolator will be used. Specify whether or
149      * not to support progressive "flywheel" behavior in flinging.
150      */
LauncherScroller(Context context, Interpolator interpolator, boolean flywheel)151     public LauncherScroller(Context context, Interpolator interpolator, boolean flywheel) {
152         mFinished = true;
153         mInterpolator = interpolator;
154         mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
155         mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
156         mFlywheel = flywheel;
157 
158         mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
159     }
160 
161     /**
162      * The amount of friction applied to flings. The default value
163      * is {@link ViewConfiguration#getScrollFriction}.
164      *
165      * @param friction A scalar dimension-less value representing the coefficient of
166      *         friction.
167      */
setFriction(float friction)168     public final void setFriction(float friction) {
169         mDeceleration = computeDeceleration(friction);
170         mFlingFriction = friction;
171     }
172 
computeDeceleration(float friction)173     private float computeDeceleration(float friction) {
174         return SensorManager.GRAVITY_EARTH   // g (m/s^2)
175                       * 39.37f               // inch/meter
176                       * mPpi                 // pixels per inch
177                       * friction;
178     }
179 
180     /**
181      *
182      * Returns whether the scroller has finished scrolling.
183      *
184      * @return True if the scroller has finished scrolling, false otherwise.
185      */
isFinished()186     public final boolean isFinished() {
187         return mFinished;
188     }
189 
190     /**
191      * Force the finished field to a particular value.
192      *
193      * @param finished The new finished value.
194      */
forceFinished(boolean finished)195     public final void forceFinished(boolean finished) {
196         mFinished = finished;
197     }
198 
199     /**
200      * Returns how long the scroll event will take, in milliseconds.
201      *
202      * @return The duration of the scroll in milliseconds.
203      */
getDuration()204     public final int getDuration() {
205         return mDuration;
206     }
207 
208     /**
209      * Returns the current X offset in the scroll.
210      *
211      * @return The new X offset as an absolute distance from the origin.
212      */
getCurrX()213     public final int getCurrX() {
214         return mCurrX;
215     }
216 
217     /**
218      * Returns the current Y offset in the scroll.
219      *
220      * @return The new Y offset as an absolute distance from the origin.
221      */
getCurrY()222     public final int getCurrY() {
223         return mCurrY;
224     }
225 
226     /**
227      * Returns the current velocity.
228      *
229      * @return The original velocity less the deceleration. Result may be
230      * negative.
231      */
getCurrVelocity()232     public float getCurrVelocity() {
233         return mMode == FLING_MODE ?
234                 mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
235     }
236 
237     /**
238      * Returns the start X offset in the scroll.
239      *
240      * @return The start X offset as an absolute distance from the origin.
241      */
getStartX()242     public final int getStartX() {
243         return mStartX;
244     }
245 
246     /**
247      * Returns the start Y offset in the scroll.
248      *
249      * @return The start Y offset as an absolute distance from the origin.
250      */
getStartY()251     public final int getStartY() {
252         return mStartY;
253     }
254 
255     /**
256      * Returns where the scroll will end. Valid only for "fling" scrolls.
257      *
258      * @return The final X offset as an absolute distance from the origin.
259      */
getFinalX()260     public final int getFinalX() {
261         return mFinalX;
262     }
263 
264     /**
265      * Returns where the scroll will end. Valid only for "fling" scrolls.
266      *
267      * @return The final Y offset as an absolute distance from the origin.
268      */
getFinalY()269     public final int getFinalY() {
270         return mFinalY;
271     }
272 
273     /**
274      * Call this when you want to know the new location.  If it returns true,
275      * the animation is not yet finished.
276      */
computeScrollOffset()277     public boolean computeScrollOffset() {
278         if (mFinished) {
279             return false;
280         }
281 
282         int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
283 
284         if (timePassed < mDuration) {
285             switch (mMode) {
286             case SCROLL_MODE:
287                 float x = timePassed * mDurationReciprocal;
288 
289                 if (mInterpolator == null)
290                     x = viscousFluid(x);
291                 else
292                     x = mInterpolator.getInterpolation(x);
293 
294                 mCurrX = mStartX + Math.round(x * mDeltaX);
295                 mCurrY = mStartY + Math.round(x * mDeltaY);
296                 break;
297             case FLING_MODE:
298                 final float t = (float) timePassed / mDuration;
299                 final int index = (int) (NB_SAMPLES * t);
300                 float distanceCoef = 1.f;
301                 float velocityCoef = 0.f;
302                 if (index < NB_SAMPLES) {
303                     final float t_inf = (float) index / NB_SAMPLES;
304                     final float t_sup = (float) (index + 1) / NB_SAMPLES;
305                     final float d_inf = SPLINE_POSITION[index];
306                     final float d_sup = SPLINE_POSITION[index + 1];
307                     velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
308                     distanceCoef = d_inf + (t - t_inf) * velocityCoef;
309                 }
310 
311                 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
312 
313                 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
314                 // Pin to mMinX <= mCurrX <= mMaxX
315                 mCurrX = Math.min(mCurrX, mMaxX);
316                 mCurrX = Math.max(mCurrX, mMinX);
317 
318                 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
319                 // Pin to mMinY <= mCurrY <= mMaxY
320                 mCurrY = Math.min(mCurrY, mMaxY);
321                 mCurrY = Math.max(mCurrY, mMinY);
322 
323                 if (mCurrX == mFinalX && mCurrY == mFinalY) {
324                     mFinished = true;
325                 }
326 
327                 break;
328             }
329         }
330         else {
331             mCurrX = mFinalX;
332             mCurrY = mFinalY;
333             mFinished = true;
334         }
335         return true;
336     }
337 
338     /**
339      * Start scrolling by providing a starting point and the distance to travel.
340      * The scroll will use the default value of 250 milliseconds for the
341      * duration.
342      *
343      * @param startX Starting horizontal scroll offset in pixels. Positive
344      *        numbers will scroll the content to the left.
345      * @param startY Starting vertical scroll offset in pixels. Positive numbers
346      *        will scroll the content up.
347      * @param dx Horizontal distance to travel. Positive numbers will scroll the
348      *        content to the left.
349      * @param dy Vertical distance to travel. Positive numbers will scroll the
350      *        content up.
351      */
startScroll(int startX, int startY, int dx, int dy)352     public void startScroll(int startX, int startY, int dx, int dy) {
353         startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
354     }
355 
356     /**
357      * Start scrolling by providing a starting point, the distance to travel,
358      * and the duration of the scroll.
359      *
360      * @param startX Starting horizontal scroll offset in pixels. Positive
361      *        numbers will scroll the content to the left.
362      * @param startY Starting vertical scroll offset in pixels. Positive numbers
363      *        will scroll the content up.
364      * @param dx Horizontal distance to travel. Positive numbers will scroll the
365      *        content to the left.
366      * @param dy Vertical distance to travel. Positive numbers will scroll the
367      *        content up.
368      * @param duration Duration of the scroll in milliseconds.
369      */
startScroll(int startX, int startY, int dx, int dy, int duration)370     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
371         mMode = SCROLL_MODE;
372         mFinished = false;
373         mDuration = duration;
374         mStartTime = AnimationUtils.currentAnimationTimeMillis();
375         mStartX = startX;
376         mStartY = startY;
377         mFinalX = startX + dx;
378         mFinalY = startY + dy;
379         mDeltaX = dx;
380         mDeltaY = dy;
381         mDurationReciprocal = 1.0f / (float) mDuration;
382     }
383 
384     /**
385      * Start scrolling based on a fling gesture. The distance travelled will
386      * depend on the initial velocity of the fling.
387      *
388      * @param startX Starting point of the scroll (X)
389      * @param startY Starting point of the scroll (Y)
390      * @param velocityX Initial velocity of the fling (X) measured in pixels per
391      *        second.
392      * @param velocityY Initial velocity of the fling (Y) measured in pixels per
393      *        second
394      * @param minX Minimum X value. The scroller will not scroll past this
395      *        point.
396      * @param maxX Maximum X value. The scroller will not scroll past this
397      *        point.
398      * @param minY Minimum Y value. The scroller will not scroll past this
399      *        point.
400      * @param maxY Maximum Y value. The scroller will not scroll past this
401      *        point.
402      */
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)403     public void fling(int startX, int startY, int velocityX, int velocityY,
404             int minX, int maxX, int minY, int maxY) {
405         // Continue a scroll or fling in progress
406         if (mFlywheel && !mFinished) {
407             float oldVel = getCurrVelocity();
408 
409             float dx = (float) (mFinalX - mStartX);
410             float dy = (float) (mFinalY - mStartY);
411             float hyp = (float) Math.hypot(dx, dy);
412 
413             float ndx = dx / hyp;
414             float ndy = dy / hyp;
415 
416             float oldVelocityX = ndx * oldVel;
417             float oldVelocityY = ndy * oldVel;
418             if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
419                     Math.signum(velocityY) == Math.signum(oldVelocityY)) {
420                 velocityX += oldVelocityX;
421                 velocityY += oldVelocityY;
422             }
423         }
424 
425         mMode = FLING_MODE;
426         mFinished = false;
427 
428         float velocity = (float) Math.hypot(velocityX, velocityY);
429 
430         mVelocity = velocity;
431         mDuration = getSplineFlingDuration(velocity);
432         mStartTime = AnimationUtils.currentAnimationTimeMillis();
433         mStartX = startX;
434         mStartY = startY;
435 
436         float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
437         float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
438 
439         double totalDistance = getSplineFlingDistance(velocity);
440         mDistance = (int) (totalDistance * Math.signum(velocity));
441 
442         mMinX = minX;
443         mMaxX = maxX;
444         mMinY = minY;
445         mMaxY = maxY;
446 
447         mFinalX = startX + (int) Math.round(totalDistance * coeffX);
448         // Pin to mMinX <= mFinalX <= mMaxX
449         mFinalX = Math.min(mFinalX, mMaxX);
450         mFinalX = Math.max(mFinalX, mMinX);
451 
452         mFinalY = startY + (int) Math.round(totalDistance * coeffY);
453         // Pin to mMinY <= mFinalY <= mMaxY
454         mFinalY = Math.min(mFinalY, mMaxY);
455         mFinalY = Math.max(mFinalY, mMinY);
456     }
457 
getSplineDeceleration(float velocity)458     private double getSplineDeceleration(float velocity) {
459         return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
460     }
461 
getSplineFlingDuration(float velocity)462     private int getSplineFlingDuration(float velocity) {
463         final double l = getSplineDeceleration(velocity);
464         final double decelMinusOne = DECELERATION_RATE - 1.0;
465         return (int) (1000.0 * Math.exp(l / decelMinusOne));
466     }
467 
getSplineFlingDistance(float velocity)468     private double getSplineFlingDistance(float velocity) {
469         final double l = getSplineDeceleration(velocity);
470         final double decelMinusOne = DECELERATION_RATE - 1.0;
471         return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
472     }
473 
viscousFluid(float x)474     static float viscousFluid(float x)
475     {
476         x *= sViscousFluidScale;
477         if (x < 1.0f) {
478             x -= (1.0f - (float)Math.exp(-x));
479         } else {
480             float start = 0.36787944117f;   // 1/e == exp(-1)
481             x = 1.0f - (float)Math.exp(1.0f - x);
482             x = start + x * (1.0f - start);
483         }
484         x *= sViscousFluidNormalize;
485         return x;
486     }
487 
488     /**
489      * Stops the animation. Contrary to {@link #forceFinished(boolean)},
490      * aborting the animating cause the scroller to move to the final x and y
491      * position
492      *
493      * @see #forceFinished(boolean)
494      */
abortAnimation()495     public void abortAnimation() {
496         mCurrX = mFinalX;
497         mCurrY = mFinalY;
498         mFinished = true;
499     }
500 
501     /**
502      * Extend the scroll animation. This allows a running animation to scroll
503      * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
504      *
505      * @param extend Additional time to scroll in milliseconds.
506      * @see #setFinalX(int)
507      * @see #setFinalY(int)
508      */
extendDuration(int extend)509     public void extendDuration(int extend) {
510         int passed = timePassed();
511         mDuration = passed + extend;
512         mDurationReciprocal = 1.0f / mDuration;
513         mFinished = false;
514     }
515 
516     /**
517      * Returns the time elapsed since the beginning of the scrolling.
518      *
519      * @return The elapsed time in milliseconds.
520      */
timePassed()521     public int timePassed() {
522         return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
523     }
524 
525     /**
526      * Sets the final position (X) for this scroller.
527      *
528      * @param newX The new X offset as an absolute distance from the origin.
529      * @see #extendDuration(int)
530      * @see #setFinalY(int)
531      */
setFinalX(int newX)532     public void setFinalX(int newX) {
533         mFinalX = newX;
534         mDeltaX = mFinalX - mStartX;
535         mFinished = false;
536     }
537 
538     /**
539      * Sets the final position (Y) for this scroller.
540      *
541      * @param newY The new Y offset as an absolute distance from the origin.
542      * @see #extendDuration(int)
543      * @see #setFinalX(int)
544      */
setFinalY(int newY)545     public void setFinalY(int newY) {
546         mFinalY = newY;
547         mDeltaY = mFinalY - mStartY;
548         mFinished = false;
549     }
550 
551     /**
552      * @hide
553      */
isScrollingInDirection(float xvel, float yvel)554     public boolean isScrollingInDirection(float xvel, float yvel) {
555         return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
556                 Math.signum(yvel) == Math.signum(mFinalY - mStartY);
557     }
558 }
559