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