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