blob: 5e88a968271e74423134b5f41b6d9ac1bc6bde24 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.content.Context;
Cyril Mottierc3129422009-05-05 18:13:48 +020020import android.hardware.SensorManager;
Gilles Debunned348bb42010-11-15 12:19:35 -080021import android.os.Build;
22import android.util.FloatMath;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.view.ViewConfiguration;
24import android.view.animation.AnimationUtils;
25import android.view.animation.Interpolator;
26
27
28/**
Katie McCormick87cfad72013-02-06 18:15:13 -080029 * <p>This class encapsulates scrolling. You can use scrollers ({@link Scroller}
30 * or {@link OverScroller}) to collect the data you need to produce a scrolling
31 * animation&mdash;for example, in response to a fling gesture. Scrollers track
32 * scroll offsets for you over time, but they don't automatically apply those
33 * positions to your view. It's your responsibility to get and apply new
34 * coordinates at a rate that will make the scrolling animation look smooth.</p>
35 *
36 * <p>Here is a simple example:</p>
37 *
38 * <pre> private Scroller mScroller = new Scroller(context);
39 * ...
40 * public void zoomIn() {
41 * // Revert any animation currently in progress
42 * mScroller.forceFinished(true);
43 * // Start scrolling by providing a starting point and
44 * // the distance to travel
45 * mScroller.startScroll(0, 0, 100, 0);
46 * // Invalidate to request a redraw
47 * invalidate();
48 * }</pre>
49 *
50 * <p>To track the changing positions of the x/y coordinates, use
51 * {@link #computeScrollOffset}. The method returns a boolean to indicate
52 * whether the scroller is finished. If it isn't, it means that a fling or
53 * programmatic pan operation is still in progress. You can use this method to
54 * find the current offsets of the x and y coordinates, for example:</p>
55 *
56 * <pre>if (mScroller.computeScrollOffset()) {
57 * // Get current x and y positions
58 * int currX = mScroller.getCurrX();
59 * int currY = mScroller.getCurrY();
60 * ...
61 * }</pre>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 */
63public class Scroller {
Alan Viveretted75e2532013-09-04 14:15:23 -070064 private final Interpolator mInterpolator;
65
Adam Powell6579b0b2010-03-25 12:21:34 -070066 private int mMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
Adam Powell9d32d242010-03-29 16:02:07 -070068 private int mStartX;
69 private int mStartY;
70 private int mFinalX;
71 private int mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072
Adam Powell9d32d242010-03-29 16:02:07 -070073 private int mMinX;
74 private int mMaxX;
75 private int mMinY;
76 private int mMaxY;
77
78 private int mCurrX;
79 private int mCurrY;
80 private long mStartTime;
81 private int mDuration;
82 private float mDurationReciprocal;
83 private float mDeltaX;
84 private float mDeltaY;
Adam Powell9d32d242010-03-29 16:02:07 -070085 private boolean mFinished;
Gilles Debunned348bb42010-11-15 12:19:35 -080086 private boolean mFlywheel;
Adam Powell9d32d242010-03-29 16:02:07 -070087
Adam Powell9d32d242010-03-29 16:02:07 -070088 private float mVelocity;
Adam Powell990dfc62012-08-09 16:45:59 -070089 private float mCurrVelocity;
90 private int mDistance;
91
92 private float mFlingFriction = ViewConfiguration.getScrollFriction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093
Adam Powell6579b0b2010-03-25 12:21:34 -070094 private static final int DEFAULT_DURATION = 250;
95 private static final int SCROLL_MODE = 0;
96 private static final int FLING_MODE = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097
Adam Powell990dfc62012-08-09 16:45:59 -070098 private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
99 private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
100 private static final float START_TENSION = 0.5f;
101 private static final float END_TENSION = 1.0f;
102 private static final float P1 = START_TENSION * INFLEXION;
103 private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
104
Gilles Debunned348bb42010-11-15 12:19:35 -0800105 private static final int NB_SAMPLES = 100;
Adam Powell990dfc62012-08-09 16:45:59 -0700106 private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
107 private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
Gilles Debunned348bb42010-11-15 12:19:35 -0800108
Romain Guy4bede9e2010-10-11 19:36:59 -0700109 private float mDeceleration;
110 private final float mPpi;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
Adam Powell990dfc62012-08-09 16:45:59 -0700112 // A context-specific coefficient adjusted to physical values.
113 private float mPhysicalCoeff;
114
Gilles Debunned348bb42010-11-15 12:19:35 -0800115 static {
116 float x_min = 0.0f;
Adam Powell990dfc62012-08-09 16:45:59 -0700117 float y_min = 0.0f;
118 for (int i = 0; i < NB_SAMPLES; i++) {
119 final float alpha = (float) i / NB_SAMPLES;
120
Gilles Debunned348bb42010-11-15 12:19:35 -0800121 float x_max = 1.0f;
122 float x, tx, coef;
123 while (true) {
124 x = x_min + (x_max - x_min) / 2.0f;
125 coef = 3.0f * x * (1.0f - x);
Adam Powell990dfc62012-08-09 16:45:59 -0700126 tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
127 if (Math.abs(tx - alpha) < 1E-5) break;
128 if (tx > alpha) x_max = x;
Gilles Debunned348bb42010-11-15 12:19:35 -0800129 else x_min = x;
130 }
Adam Powell990dfc62012-08-09 16:45:59 -0700131 SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
132
133 float y_max = 1.0f;
134 float y, dy;
135 while (true) {
136 y = y_min + (y_max - y_min) / 2.0f;
137 coef = 3.0f * y * (1.0f - y);
138 dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
139 if (Math.abs(dy - alpha) < 1E-5) break;
140 if (dy > alpha) y_max = y;
141 else y_min = y;
142 }
143 SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
Gilles Debunned348bb42010-11-15 12:19:35 -0800144 }
Adam Powell990dfc62012-08-09 16:45:59 -0700145 SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
Gilles Debunned348bb42010-11-15 12:19:35 -0800146 }
147
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700149 * Create a Scroller with the default duration and interpolator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 */
151 public Scroller(Context context) {
152 this(context, null);
153 }
154
Adam Powell637d3372010-08-25 14:37:03 -0700155 /**
156 * Create a Scroller with the specified interpolator. If the interpolator is
157 * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
158 * be in effect for apps targeting Honeycomb or newer.
159 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800160 public Scroller(Context context, Interpolator interpolator) {
161 this(context, interpolator,
162 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
163 }
164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 /**
166 * Create a Scroller with the specified interpolator. If the interpolator is
Adam Powell637d3372010-08-25 14:37:03 -0700167 * null, the default (viscous) interpolator will be used. Specify whether or
168 * not to support progressive "flywheel" behavior in flinging.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800170 public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
Adam Powell9d32d242010-03-29 16:02:07 -0700171 mFinished = true;
Alan Viveretted75e2532013-09-04 14:15:23 -0700172 if (interpolator == null) {
173 mInterpolator = new ViscousFluidInterpolator();
174 } else {
175 mInterpolator = interpolator;
176 }
Romain Guy4bede9e2010-10-11 19:36:59 -0700177 mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
178 mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
Gilles Debunned348bb42010-11-15 12:19:35 -0800179 mFlywheel = flywheel;
Adam Powell990dfc62012-08-09 16:45:59 -0700180
181 mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
Romain Guy4bede9e2010-10-11 19:36:59 -0700182 }
183
184 /**
185 * The amount of friction applied to flings. The default value
186 * is {@link ViewConfiguration#getScrollFriction}.
187 *
Gilles Debunned348bb42010-11-15 12:19:35 -0800188 * @param friction A scalar dimension-less value representing the coefficient of
Romain Guy4bede9e2010-10-11 19:36:59 -0700189 * friction.
190 */
191 public final void setFriction(float friction) {
Romain Guya655c632010-10-12 10:19:25 -0700192 mDeceleration = computeDeceleration(friction);
Adam Powell990dfc62012-08-09 16:45:59 -0700193 mFlingFriction = friction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 }
Adam Powell9d32d242010-03-29 16:02:07 -0700195
Romain Guy4bede9e2010-10-11 19:36:59 -0700196 private float computeDeceleration(float friction) {
197 return SensorManager.GRAVITY_EARTH // g (m/s^2)
198 * 39.37f // inch/meter
199 * mPpi // pixels per inch
200 * friction;
201 }
202
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800203 /**
204 *
205 * Returns whether the scroller has finished scrolling.
206 *
207 * @return True if the scroller has finished scrolling, false otherwise.
208 */
209 public final boolean isFinished() {
Adam Powell9d32d242010-03-29 16:02:07 -0700210 return mFinished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800211 }
Adam Powell9d32d242010-03-29 16:02:07 -0700212
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 /**
214 * Force the finished field to a particular value.
Adam Powell9d32d242010-03-29 16:02:07 -0700215 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 * @param finished The new finished value.
217 */
218 public final void forceFinished(boolean finished) {
Adam Powell9d32d242010-03-29 16:02:07 -0700219 mFinished = finished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 }
Adam Powell9d32d242010-03-29 16:02:07 -0700221
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 /**
223 * Returns how long the scroll event will take, in milliseconds.
224 *
225 * @return The duration of the scroll in milliseconds.
226 */
227 public final int getDuration() {
Adam Powell9d32d242010-03-29 16:02:07 -0700228 return mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 }
Adam Powell9d32d242010-03-29 16:02:07 -0700230
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700232 * Returns the current X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 *
234 * @return The new X offset as an absolute distance from the origin.
235 */
236 public final int getCurrX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700237 return mCurrX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 }
Adam Powell9d32d242010-03-29 16:02:07 -0700239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700241 * Returns the current Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 *
243 * @return The new Y offset as an absolute distance from the origin.
244 */
245 public final int getCurrY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700246 return mCurrY;
Cary Clark278ce052009-08-31 16:08:42 -0400247 }
Adam Powell9d32d242010-03-29 16:02:07 -0700248
Cary Clark278ce052009-08-31 16:08:42 -0400249 /**
Gilles Debunne52964242010-02-24 11:05:19 -0800250 * Returns the current velocity.
Adam Powell9d32d242010-03-29 16:02:07 -0700251 *
252 * @return The original velocity less the deceleration. Result may be
253 * negative.
Gilles Debunne52964242010-02-24 11:05:19 -0800254 */
255 public float getCurrVelocity() {
Adam Powell990dfc62012-08-09 16:45:59 -0700256 return mMode == FLING_MODE ?
257 mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
Gilles Debunne52964242010-02-24 11:05:19 -0800258 }
259
260 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700261 * Returns the start X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800262 *
263 * @return The start X offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 */
265 public final int getStartX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700266 return mStartX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 }
Adam Powell9d32d242010-03-29 16:02:07 -0700268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700270 * Returns the start Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 *
272 * @return The start Y offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 */
274 public final int getStartY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700275 return mStartY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 }
Adam Powell9d32d242010-03-29 16:02:07 -0700277
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 /**
279 * Returns where the scroll will end. Valid only for "fling" scrolls.
280 *
281 * @return The final X offset as an absolute distance from the origin.
282 */
283 public final int getFinalX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700284 return mFinalX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 }
Adam Powell9d32d242010-03-29 16:02:07 -0700286
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800287 /**
288 * Returns where the scroll will end. Valid only for "fling" scrolls.
289 *
290 * @return The final Y offset as an absolute distance from the origin.
291 */
292 public final int getFinalY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700293 return mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 }
295
296 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700297 * Call this when you want to know the new location. If it returns true,
Katie McCormick87cfad72013-02-06 18:15:13 -0800298 * the animation is not yet finished.
Adam Powell9d32d242010-03-29 16:02:07 -0700299 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 public boolean computeScrollOffset() {
Adam Powell9d32d242010-03-29 16:02:07 -0700301 if (mFinished) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 return false;
303 }
304
Adam Powell9d32d242010-03-29 16:02:07 -0700305 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
306
307 if (timePassed < mDuration) {
308 switch (mMode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 case SCROLL_MODE:
Alan Viveretted75e2532013-09-04 14:15:23 -0700310 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
Adam Powell9d32d242010-03-29 16:02:07 -0700311 mCurrX = mStartX + Math.round(x * mDeltaX);
312 mCurrY = mStartY + Math.round(x * mDeltaY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800313 break;
Gilles Debunne52964242010-02-24 11:05:19 -0800314 case FLING_MODE:
Gilles Debunned348bb42010-11-15 12:19:35 -0800315 final float t = (float) timePassed / mDuration;
316 final int index = (int) (NB_SAMPLES * t);
Adam Powell990dfc62012-08-09 16:45:59 -0700317 float distanceCoef = 1.f;
318 float velocityCoef = 0.f;
319 if (index < NB_SAMPLES) {
320 final float t_inf = (float) index / NB_SAMPLES;
321 final float t_sup = (float) (index + 1) / NB_SAMPLES;
322 final float d_inf = SPLINE_POSITION[index];
323 final float d_sup = SPLINE_POSITION[index + 1];
324 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
325 distanceCoef = d_inf + (t - t_inf) * velocityCoef;
326 }
327
328 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
Adam Powell9d32d242010-03-29 16:02:07 -0700329
Gilles Debunned348bb42010-11-15 12:19:35 -0800330 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
Adam Powell9d32d242010-03-29 16:02:07 -0700331 // Pin to mMinX <= mCurrX <= mMaxX
332 mCurrX = Math.min(mCurrX, mMaxX);
333 mCurrX = Math.max(mCurrX, mMinX);
334
Gilles Debunned348bb42010-11-15 12:19:35 -0800335 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
Adam Powell9d32d242010-03-29 16:02:07 -0700336 // Pin to mMinY <= mCurrY <= mMaxY
337 mCurrY = Math.min(mCurrY, mMaxY);
338 mCurrY = Math.max(mCurrY, mMinY);
Adam Powell1b088be2010-07-23 15:49:03 -0700339
340 if (mCurrX == mFinalX && mCurrY == mFinalY) {
341 mFinished = true;
342 }
343
Gilles Debunne52964242010-02-24 11:05:19 -0800344 break;
Adam Powell9d32d242010-03-29 16:02:07 -0700345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 }
Adam Powell9d32d242010-03-29 16:02:07 -0700347 else {
348 mCurrX = mFinalX;
349 mCurrY = mFinalY;
350 mFinished = true;
351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 return true;
353 }
Adam Powell9d32d242010-03-29 16:02:07 -0700354
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 /**
356 * Start scrolling by providing a starting point and the distance to travel.
357 * The scroll will use the default value of 250 milliseconds for the
358 * duration.
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 */
369 public void startScroll(int startX, int startY, int dx, int dy) {
370 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
371 }
372
373 /**
Katie McCormick87cfad72013-02-06 18:15:13 -0800374 * Start scrolling by providing a starting point, the distance to travel,
375 * and the duration of the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 *
377 * @param startX Starting horizontal scroll offset in pixels. Positive
378 * numbers will scroll the content to the left.
379 * @param startY Starting vertical scroll offset in pixels. Positive numbers
380 * will scroll the content up.
381 * @param dx Horizontal distance to travel. Positive numbers will scroll the
382 * content to the left.
383 * @param dy Vertical distance to travel. Positive numbers will scroll the
384 * content up.
385 * @param duration Duration of the scroll in milliseconds.
386 */
387 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
388 mMode = SCROLL_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700389 mFinished = false;
390 mDuration = duration;
391 mStartTime = AnimationUtils.currentAnimationTimeMillis();
392 mStartX = startX;
393 mStartY = startY;
394 mFinalX = startX + dx;
395 mFinalY = startY + dy;
396 mDeltaX = dx;
397 mDeltaY = dy;
Adam Powell637d3372010-08-25 14:37:03 -0700398 mDurationReciprocal = 1.0f / (float) mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800399 }
400
401 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700402 * Start scrolling based on a fling gesture. The distance travelled will
403 * depend on the initial velocity of the fling.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 *
405 * @param startX Starting point of the scroll (X)
406 * @param startY Starting point of the scroll (Y)
407 * @param velocityX Initial velocity of the fling (X) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700408 * second.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700410 * second
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800411 * @param minX Minimum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700412 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 * @param maxX Maximum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700414 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 * @param minY Minimum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700416 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 * @param maxY Maximum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700418 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 */
420 public void fling(int startX, int startY, int velocityX, int velocityY,
421 int minX, int maxX, int minY, int maxY) {
Gilles Debunned348bb42010-11-15 12:19:35 -0800422 // Continue a scroll or fling in progress
423 if (mFlywheel && !mFinished) {
424 float oldVel = getCurrVelocity();
425
426 float dx = (float) (mFinalX - mStartX);
427 float dy = (float) (mFinalY - mStartY);
428 float hyp = FloatMath.sqrt(dx * dx + dy * dy);
429
430 float ndx = dx / hyp;
431 float ndy = dy / hyp;
432
433 float oldVelocityX = ndx * oldVel;
434 float oldVelocityY = ndy * oldVel;
435 if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
436 Math.signum(velocityY) == Math.signum(oldVelocityY)) {
437 velocityX += oldVelocityX;
438 velocityY += oldVelocityY;
439 }
440 }
441
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800442 mMode = FLING_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700443 mFinished = false;
Gilles Debunne52964242010-02-24 11:05:19 -0800444
Gilles Debunned348bb42010-11-15 12:19:35 -0800445 float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
Adam Powell9d32d242010-03-29 16:02:07 -0700446
447 mVelocity = velocity;
Adam Powell990dfc62012-08-09 16:45:59 -0700448 mDuration = getSplineFlingDuration(velocity);
Adam Powell9d32d242010-03-29 16:02:07 -0700449 mStartTime = AnimationUtils.currentAnimationTimeMillis();
450 mStartX = startX;
451 mStartY = startY;
452
Adam Powell637d3372010-08-25 14:37:03 -0700453 float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
454 float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
Adam Powell9d32d242010-03-29 16:02:07 -0700455
Adam Powell990dfc62012-08-09 16:45:59 -0700456 double totalDistance = getSplineFlingDistance(velocity);
457 mDistance = (int) (totalDistance * Math.signum(velocity));
Adam Powell9d32d242010-03-29 16:02:07 -0700458
459 mMinX = minX;
460 mMaxX = maxX;
461 mMinY = minY;
462 mMaxY = maxY;
Gilles Debunned348bb42010-11-15 12:19:35 -0800463
Adam Powell990dfc62012-08-09 16:45:59 -0700464 mFinalX = startX + (int) Math.round(totalDistance * coeffX);
Adam Powell9d32d242010-03-29 16:02:07 -0700465 // Pin to mMinX <= mFinalX <= mMaxX
466 mFinalX = Math.min(mFinalX, mMaxX);
467 mFinalX = Math.max(mFinalX, mMinX);
468
Adam Powell990dfc62012-08-09 16:45:59 -0700469 mFinalY = startY + (int) Math.round(totalDistance * coeffY);
Adam Powell9d32d242010-03-29 16:02:07 -0700470 // Pin to mMinY <= mFinalY <= mMaxY
471 mFinalY = Math.min(mFinalY, mMaxY);
472 mFinalY = Math.max(mFinalY, mMinY);
473 }
474
Adam Powell990dfc62012-08-09 16:45:59 -0700475 private double getSplineDeceleration(float velocity) {
476 return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
477 }
478
479 private int getSplineFlingDuration(float velocity) {
480 final double l = getSplineDeceleration(velocity);
481 final double decelMinusOne = DECELERATION_RATE - 1.0;
482 return (int) (1000.0 * Math.exp(l / decelMinusOne));
483 }
484
485 private double getSplineFlingDistance(float velocity) {
486 final double l = getSplineDeceleration(velocity);
487 final double decelMinusOne = DECELERATION_RATE - 1.0;
488 return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
489 }
490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200492 * Stops the animation. Contrary to {@link #forceFinished(boolean)},
493 * aborting the animating cause the scroller to move to the final x and y
494 * position
Adam Powell9d32d242010-03-29 16:02:07 -0700495 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200496 * @see #forceFinished(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 */
498 public void abortAnimation() {
Adam Powell9d32d242010-03-29 16:02:07 -0700499 mCurrX = mFinalX;
500 mCurrY = mFinalY;
501 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800502 }
Adam Powell9d32d242010-03-29 16:02:07 -0700503
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800504 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200505 * Extend the scroll animation. This allows a running animation to scroll
djkend663dab2009-05-08 07:07:55 -0400506 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
Adam Powell9d32d242010-03-29 16:02:07 -0700507 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 * @param extend Additional time to scroll in milliseconds.
Cyril Mottierc3129422009-05-05 18:13:48 +0200509 * @see #setFinalX(int)
510 * @see #setFinalY(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800511 */
512 public void extendDuration(int extend) {
Adam Powell9d32d242010-03-29 16:02:07 -0700513 int passed = timePassed();
514 mDuration = passed + extend;
Gilles Debunned348bb42010-11-15 12:19:35 -0800515 mDurationReciprocal = 1.0f / mDuration;
Adam Powell9d32d242010-03-29 16:02:07 -0700516 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200518
519 /**
520 * Returns the time elapsed since the beginning of the scrolling.
Adam Powell9d32d242010-03-29 16:02:07 -0700521 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200522 * @return The elapsed time in milliseconds.
523 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 public int timePassed() {
Adam Powell9d32d242010-03-29 16:02:07 -0700525 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800526 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200527
528 /**
529 * Sets the final position (X) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700530 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200531 * @param newX The new X offset as an absolute distance from the origin.
532 * @see #extendDuration(int)
533 * @see #setFinalY(int)
534 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800535 public void setFinalX(int newX) {
Adam Powell9d32d242010-03-29 16:02:07 -0700536 mFinalX = newX;
537 mDeltaX = mFinalX - mStartX;
538 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800539 }
540
Cyril Mottierc3129422009-05-05 18:13:48 +0200541 /**
542 * Sets the final position (Y) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700543 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200544 * @param newY The new Y offset as an absolute distance from the origin.
545 * @see #extendDuration(int)
546 * @see #setFinalX(int)
547 */
548 public void setFinalY(int newY) {
Adam Powell9d32d242010-03-29 16:02:07 -0700549 mFinalY = newY;
550 mDeltaY = mFinalY - mStartY;
551 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 }
Gilles Debunned348bb42010-11-15 12:19:35 -0800553
554 /**
555 * @hide
556 */
557 public boolean isScrollingInDirection(float xvel, float yvel) {
558 return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
559 Math.signum(yvel) == Math.signum(mFinalY - mStartY);
560 }
Alan Viveretted75e2532013-09-04 14:15:23 -0700561
Alan Viverette79a5fef2013-09-04 16:37:57 -0700562 static class ViscousFluidInterpolator implements Interpolator {
Alan Viveretted75e2532013-09-04 14:15:23 -0700563 /** Controls the viscous fluid effect (how much of it). */
564 private static final float VISCOUS_FLUID_SCALE = 8.0f;
565
566 private static final float VISCOUS_FLUID_NORMALIZE;
567 private static final float VISCOUS_FLUID_OFFSET;
568
569 static {
570
571 // must be set to 1.0 (used in viscousFluid())
572 VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);
573 // account for very small floating-point error
574 VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);
575 }
576
577 private static float viscousFluid(float x) {
578 x *= VISCOUS_FLUID_SCALE;
579 if (x < 1.0f) {
580 x -= (1.0f - (float)Math.exp(-x));
581 } else {
582 float start = 0.36787944117f; // 1/e == exp(-1)
583 x = 1.0f - (float)Math.exp(1.0f - x);
584 x = start + x * (1.0f - start);
585 }
586 return x;
587 }
588
589 @Override
590 public float getInterpolation(float input) {
591 final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);
Chet Haase54b9e5b2014-08-05 16:56:49 -0700592 if (interpolated > 0) {
593 return interpolated + VISCOUS_FLUID_OFFSET;
Alan Viveretted75e2532013-09-04 14:15:23 -0700594 }
Chet Haase54b9e5b2014-08-05 16:56:49 -0700595 return interpolated;
Alan Viveretted75e2532013-09-04 14:15:23 -0700596 }
597 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800598}