blob: 3bfd39d80baa229530fa185d88ddc991a3edc2d2 [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 {
Adam Powell6579b0b2010-03-25 12:21:34 -070064 private int mMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
Adam Powell9d32d242010-03-29 16:02:07 -070066 private int mStartX;
67 private int mStartY;
68 private int mFinalX;
69 private int mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
Adam Powell9d32d242010-03-29 16:02:07 -070071 private int mMinX;
72 private int mMaxX;
73 private int mMinY;
74 private int mMaxY;
75
76 private int mCurrX;
77 private int mCurrY;
78 private long mStartTime;
79 private int mDuration;
80 private float mDurationReciprocal;
81 private float mDeltaX;
82 private float mDeltaY;
Adam Powell9d32d242010-03-29 16:02:07 -070083 private boolean mFinished;
84 private Interpolator mInterpolator;
Gilles Debunned348bb42010-11-15 12:19:35 -080085 private boolean mFlywheel;
Adam Powell9d32d242010-03-29 16:02:07 -070086
Adam Powell9d32d242010-03-29 16:02:07 -070087 private float mVelocity;
Adam Powell990dfc6f2012-08-09 16:45:59 -070088 private float mCurrVelocity;
89 private int mDistance;
90
91 private float mFlingFriction = ViewConfiguration.getScrollFriction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092
Adam Powell6579b0b2010-03-25 12:21:34 -070093 private static final int DEFAULT_DURATION = 250;
94 private static final int SCROLL_MODE = 0;
95 private static final int FLING_MODE = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096
Adam Powell990dfc6f2012-08-09 16:45:59 -070097 private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
98 private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
99 private static final float START_TENSION = 0.5f;
100 private static final float END_TENSION = 1.0f;
101 private static final float P1 = START_TENSION * INFLEXION;
102 private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
103
Gilles Debunned348bb42010-11-15 12:19:35 -0800104 private static final int NB_SAMPLES = 100;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700105 private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
106 private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
Gilles Debunned348bb42010-11-15 12:19:35 -0800107
Romain Guy4bede9e2010-10-11 19:36:59 -0700108 private float mDeceleration;
109 private final float mPpi;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800110
Adam Powell990dfc6f2012-08-09 16:45:59 -0700111 // A context-specific coefficient adjusted to physical values.
112 private float mPhysicalCoeff;
113
Gilles Debunned348bb42010-11-15 12:19:35 -0800114 static {
115 float x_min = 0.0f;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700116 float y_min = 0.0f;
117 for (int i = 0; i < NB_SAMPLES; i++) {
118 final float alpha = (float) i / NB_SAMPLES;
119
Gilles Debunned348bb42010-11-15 12:19:35 -0800120 float x_max = 1.0f;
121 float x, tx, coef;
122 while (true) {
123 x = x_min + (x_max - x_min) / 2.0f;
124 coef = 3.0f * x * (1.0f - x);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700125 tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
126 if (Math.abs(tx - alpha) < 1E-5) break;
127 if (tx > alpha) x_max = x;
Gilles Debunned348bb42010-11-15 12:19:35 -0800128 else x_min = x;
129 }
Adam Powell990dfc6f2012-08-09 16:45:59 -0700130 SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
131
132 float y_max = 1.0f;
133 float y, dy;
134 while (true) {
135 y = y_min + (y_max - y_min) / 2.0f;
136 coef = 3.0f * y * (1.0f - y);
137 dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
138 if (Math.abs(dy - alpha) < 1E-5) break;
139 if (dy > alpha) y_max = y;
140 else y_min = y;
141 }
142 SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
Gilles Debunned348bb42010-11-15 12:19:35 -0800143 }
Adam Powell990dfc6f2012-08-09 16:45:59 -0700144 SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
Adam Powell637d3372010-08-25 14:37:03 -0700145
146 // This controls the viscous fluid effect (how much of it)
147 sViscousFluidScale = 8.0f;
148 // must be set to 1.0 (used in viscousFluid())
149 sViscousFluidNormalize = 1.0f;
150 sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700151
Gilles Debunned348bb42010-11-15 12:19:35 -0800152 }
153
Adam Powell637d3372010-08-25 14:37:03 -0700154 private static float sViscousFluidScale;
155 private static float sViscousFluidNormalize;
156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700158 * Create a Scroller with the default duration and interpolator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 */
160 public Scroller(Context context) {
161 this(context, null);
162 }
163
Adam Powell637d3372010-08-25 14:37:03 -0700164 /**
165 * Create a Scroller with the specified interpolator. If the interpolator is
166 * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
167 * be in effect for apps targeting Honeycomb or newer.
168 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800169 public Scroller(Context context, Interpolator interpolator) {
170 this(context, interpolator,
171 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
172 }
173
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800174 /**
175 * Create a Scroller with the specified interpolator. If the interpolator is
Adam Powell637d3372010-08-25 14:37:03 -0700176 * null, the default (viscous) interpolator will be used. Specify whether or
177 * not to support progressive "flywheel" behavior in flinging.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800179 public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
Adam Powell9d32d242010-03-29 16:02:07 -0700180 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 mInterpolator = interpolator;
Romain Guy4bede9e2010-10-11 19:36:59 -0700182 mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
183 mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
Gilles Debunned348bb42010-11-15 12:19:35 -0800184 mFlywheel = flywheel;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700185
186 mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
Romain Guy4bede9e2010-10-11 19:36:59 -0700187 }
188
189 /**
190 * The amount of friction applied to flings. The default value
191 * is {@link ViewConfiguration#getScrollFriction}.
192 *
Gilles Debunned348bb42010-11-15 12:19:35 -0800193 * @param friction A scalar dimension-less value representing the coefficient of
Romain Guy4bede9e2010-10-11 19:36:59 -0700194 * friction.
195 */
196 public final void setFriction(float friction) {
Romain Guya655c632010-10-12 10:19:25 -0700197 mDeceleration = computeDeceleration(friction);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700198 mFlingFriction = friction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 }
Adam Powell9d32d242010-03-29 16:02:07 -0700200
Romain Guy4bede9e2010-10-11 19:36:59 -0700201 private float computeDeceleration(float friction) {
202 return SensorManager.GRAVITY_EARTH // g (m/s^2)
203 * 39.37f // inch/meter
204 * mPpi // pixels per inch
205 * friction;
206 }
207
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 /**
209 *
210 * Returns whether the scroller has finished scrolling.
211 *
212 * @return True if the scroller has finished scrolling, false otherwise.
213 */
214 public final boolean isFinished() {
Adam Powell9d32d242010-03-29 16:02:07 -0700215 return mFinished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 }
Adam Powell9d32d242010-03-29 16:02:07 -0700217
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 /**
219 * Force the finished field to a particular value.
Adam Powell9d32d242010-03-29 16:02:07 -0700220 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 * @param finished The new finished value.
222 */
223 public final void forceFinished(boolean finished) {
Adam Powell9d32d242010-03-29 16:02:07 -0700224 mFinished = finished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 }
Adam Powell9d32d242010-03-29 16:02:07 -0700226
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 /**
228 * Returns how long the scroll event will take, in milliseconds.
229 *
230 * @return The duration of the scroll in milliseconds.
231 */
232 public final int getDuration() {
Adam Powell9d32d242010-03-29 16:02:07 -0700233 return mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800234 }
Adam Powell9d32d242010-03-29 16:02:07 -0700235
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700237 * Returns the current X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 *
239 * @return The new X offset as an absolute distance from the origin.
240 */
241 public final int getCurrX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700242 return mCurrX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 }
Adam Powell9d32d242010-03-29 16:02:07 -0700244
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700246 * Returns the current Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800247 *
248 * @return The new Y offset as an absolute distance from the origin.
249 */
250 public final int getCurrY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700251 return mCurrY;
Cary Clark278ce052009-08-31 16:08:42 -0400252 }
Adam Powell9d32d242010-03-29 16:02:07 -0700253
Cary Clark278ce052009-08-31 16:08:42 -0400254 /**
Gilles Debunne52964242010-02-24 11:05:19 -0800255 * Returns the current velocity.
Adam Powell9d32d242010-03-29 16:02:07 -0700256 *
257 * @return The original velocity less the deceleration. Result may be
258 * negative.
Gilles Debunne52964242010-02-24 11:05:19 -0800259 */
260 public float getCurrVelocity() {
Adam Powell990dfc6f2012-08-09 16:45:59 -0700261 return mMode == FLING_MODE ?
262 mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
Gilles Debunne52964242010-02-24 11:05:19 -0800263 }
264
265 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700266 * Returns the start X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 *
268 * @return The start X offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 */
270 public final int getStartX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700271 return mStartX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 }
Adam Powell9d32d242010-03-29 16:02:07 -0700273
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700275 * Returns the start Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 *
277 * @return The start Y offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 */
279 public final int getStartY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700280 return mStartY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 }
Adam Powell9d32d242010-03-29 16:02:07 -0700282
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 /**
284 * Returns where the scroll will end. Valid only for "fling" scrolls.
285 *
286 * @return The final X offset as an absolute distance from the origin.
287 */
288 public final int getFinalX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700289 return mFinalX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800290 }
Adam Powell9d32d242010-03-29 16:02:07 -0700291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 /**
293 * Returns where the scroll will end. Valid only for "fling" scrolls.
294 *
295 * @return The final Y offset as an absolute distance from the origin.
296 */
297 public final int getFinalY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700298 return mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 }
300
301 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700302 * Call this when you want to know the new location. If it returns true,
Katie McCormick87cfad72013-02-06 18:15:13 -0800303 * the animation is not yet finished.
Adam Powell9d32d242010-03-29 16:02:07 -0700304 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800305 public boolean computeScrollOffset() {
Adam Powell9d32d242010-03-29 16:02:07 -0700306 if (mFinished) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800307 return false;
308 }
309
Adam Powell9d32d242010-03-29 16:02:07 -0700310 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
311
312 if (timePassed < mDuration) {
313 switch (mMode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800314 case SCROLL_MODE:
Gilles Debunned348bb42010-11-15 12:19:35 -0800315 float x = timePassed * mDurationReciprocal;
Adam Powell9d32d242010-03-29 16:02:07 -0700316
317 if (mInterpolator == null)
318 x = viscousFluid(x);
319 else
320 x = mInterpolator.getInterpolation(x);
321
322 mCurrX = mStartX + Math.round(x * mDeltaX);
323 mCurrY = mStartY + Math.round(x * mDeltaY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 break;
Gilles Debunne52964242010-02-24 11:05:19 -0800325 case FLING_MODE:
Gilles Debunned348bb42010-11-15 12:19:35 -0800326 final float t = (float) timePassed / mDuration;
327 final int index = (int) (NB_SAMPLES * t);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700328 float distanceCoef = 1.f;
329 float velocityCoef = 0.f;
330 if (index < NB_SAMPLES) {
331 final float t_inf = (float) index / NB_SAMPLES;
332 final float t_sup = (float) (index + 1) / NB_SAMPLES;
333 final float d_inf = SPLINE_POSITION[index];
334 final float d_sup = SPLINE_POSITION[index + 1];
335 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
336 distanceCoef = d_inf + (t - t_inf) * velocityCoef;
337 }
338
339 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
Adam Powell9d32d242010-03-29 16:02:07 -0700340
Gilles Debunned348bb42010-11-15 12:19:35 -0800341 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
Adam Powell9d32d242010-03-29 16:02:07 -0700342 // Pin to mMinX <= mCurrX <= mMaxX
343 mCurrX = Math.min(mCurrX, mMaxX);
344 mCurrX = Math.max(mCurrX, mMinX);
345
Gilles Debunned348bb42010-11-15 12:19:35 -0800346 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
Adam Powell9d32d242010-03-29 16:02:07 -0700347 // Pin to mMinY <= mCurrY <= mMaxY
348 mCurrY = Math.min(mCurrY, mMaxY);
349 mCurrY = Math.max(mCurrY, mMinY);
Adam Powell1b088be2010-07-23 15:49:03 -0700350
351 if (mCurrX == mFinalX && mCurrY == mFinalY) {
352 mFinished = true;
353 }
354
Gilles Debunne52964242010-02-24 11:05:19 -0800355 break;
Adam Powell9d32d242010-03-29 16:02:07 -0700356 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800357 }
Adam Powell9d32d242010-03-29 16:02:07 -0700358 else {
359 mCurrX = mFinalX;
360 mCurrY = mFinalY;
361 mFinished = true;
362 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800363 return true;
364 }
Adam Powell9d32d242010-03-29 16:02:07 -0700365
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 /**
367 * Start scrolling by providing a starting point and the distance to travel.
368 * The scroll will use the default value of 250 milliseconds for the
369 * duration.
370 *
371 * @param startX Starting horizontal scroll offset in pixels. Positive
372 * numbers will scroll the content to the left.
373 * @param startY Starting vertical scroll offset in pixels. Positive numbers
374 * will scroll the content up.
375 * @param dx Horizontal distance to travel. Positive numbers will scroll the
376 * content to the left.
377 * @param dy Vertical distance to travel. Positive numbers will scroll the
378 * content up.
379 */
380 public void startScroll(int startX, int startY, int dx, int dy) {
381 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
382 }
383
384 /**
Katie McCormick87cfad72013-02-06 18:15:13 -0800385 * Start scrolling by providing a starting point, the distance to travel,
386 * and the duration of the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 *
388 * @param startX Starting horizontal scroll offset in pixels. Positive
389 * numbers will scroll the content to the left.
390 * @param startY Starting vertical scroll offset in pixels. Positive numbers
391 * will scroll the content up.
392 * @param dx Horizontal distance to travel. Positive numbers will scroll the
393 * content to the left.
394 * @param dy Vertical distance to travel. Positive numbers will scroll the
395 * content up.
396 * @param duration Duration of the scroll in milliseconds.
397 */
398 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
399 mMode = SCROLL_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700400 mFinished = false;
401 mDuration = duration;
402 mStartTime = AnimationUtils.currentAnimationTimeMillis();
403 mStartX = startX;
404 mStartY = startY;
405 mFinalX = startX + dx;
406 mFinalY = startY + dy;
407 mDeltaX = dx;
408 mDeltaY = dy;
Adam Powell637d3372010-08-25 14:37:03 -0700409 mDurationReciprocal = 1.0f / (float) mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 }
411
412 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700413 * Start scrolling based on a fling gesture. The distance travelled will
414 * depend on the initial velocity of the fling.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 *
416 * @param startX Starting point of the scroll (X)
417 * @param startY Starting point of the scroll (Y)
418 * @param velocityX Initial velocity of the fling (X) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700419 * second.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700421 * second
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 * @param minX Minimum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700423 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 * @param maxX Maximum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700425 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800426 * @param minY Minimum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700427 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800428 * @param maxY Maximum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700429 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 */
431 public void fling(int startX, int startY, int velocityX, int velocityY,
432 int minX, int maxX, int minY, int maxY) {
Gilles Debunned348bb42010-11-15 12:19:35 -0800433 // Continue a scroll or fling in progress
434 if (mFlywheel && !mFinished) {
435 float oldVel = getCurrVelocity();
436
437 float dx = (float) (mFinalX - mStartX);
438 float dy = (float) (mFinalY - mStartY);
439 float hyp = FloatMath.sqrt(dx * dx + dy * dy);
440
441 float ndx = dx / hyp;
442 float ndy = dy / hyp;
443
444 float oldVelocityX = ndx * oldVel;
445 float oldVelocityY = ndy * oldVel;
446 if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
447 Math.signum(velocityY) == Math.signum(oldVelocityY)) {
448 velocityX += oldVelocityX;
449 velocityY += oldVelocityY;
450 }
451 }
452
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800453 mMode = FLING_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700454 mFinished = false;
Gilles Debunne52964242010-02-24 11:05:19 -0800455
Gilles Debunned348bb42010-11-15 12:19:35 -0800456 float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
Adam Powell9d32d242010-03-29 16:02:07 -0700457
458 mVelocity = velocity;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700459 mDuration = getSplineFlingDuration(velocity);
Adam Powell9d32d242010-03-29 16:02:07 -0700460 mStartTime = AnimationUtils.currentAnimationTimeMillis();
461 mStartX = startX;
462 mStartY = startY;
463
Adam Powell637d3372010-08-25 14:37:03 -0700464 float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
465 float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
Adam Powell9d32d242010-03-29 16:02:07 -0700466
Adam Powell990dfc6f2012-08-09 16:45:59 -0700467 double totalDistance = getSplineFlingDistance(velocity);
468 mDistance = (int) (totalDistance * Math.signum(velocity));
Adam Powell9d32d242010-03-29 16:02:07 -0700469
470 mMinX = minX;
471 mMaxX = maxX;
472 mMinY = minY;
473 mMaxY = maxY;
Gilles Debunned348bb42010-11-15 12:19:35 -0800474
Adam Powell990dfc6f2012-08-09 16:45:59 -0700475 mFinalX = startX + (int) Math.round(totalDistance * coeffX);
Adam Powell9d32d242010-03-29 16:02:07 -0700476 // Pin to mMinX <= mFinalX <= mMaxX
477 mFinalX = Math.min(mFinalX, mMaxX);
478 mFinalX = Math.max(mFinalX, mMinX);
479
Adam Powell990dfc6f2012-08-09 16:45:59 -0700480 mFinalY = startY + (int) Math.round(totalDistance * coeffY);
Adam Powell9d32d242010-03-29 16:02:07 -0700481 // Pin to mMinY <= mFinalY <= mMaxY
482 mFinalY = Math.min(mFinalY, mMaxY);
483 mFinalY = Math.max(mFinalY, mMinY);
484 }
485
Adam Powell990dfc6f2012-08-09 16:45:59 -0700486 private double getSplineDeceleration(float velocity) {
487 return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
488 }
489
490 private int getSplineFlingDuration(float velocity) {
491 final double l = getSplineDeceleration(velocity);
492 final double decelMinusOne = DECELERATION_RATE - 1.0;
493 return (int) (1000.0 * Math.exp(l / decelMinusOne));
494 }
495
496 private double getSplineFlingDistance(float velocity) {
497 final double l = getSplineDeceleration(velocity);
498 final double decelMinusOne = DECELERATION_RATE - 1.0;
499 return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
500 }
501
Adam Powell637d3372010-08-25 14:37:03 -0700502 static float viscousFluid(float x)
Adam Powell9d32d242010-03-29 16:02:07 -0700503 {
Adam Powell637d3372010-08-25 14:37:03 -0700504 x *= sViscousFluidScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800505 if (x < 1.0f) {
506 x -= (1.0f - (float)Math.exp(-x));
507 } else {
508 float start = 0.36787944117f; // 1/e == exp(-1)
509 x = 1.0f - (float)Math.exp(1.0f - x);
510 x = start + x * (1.0f - start);
511 }
Adam Powell637d3372010-08-25 14:37:03 -0700512 x *= sViscousFluidNormalize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 return x;
514 }
Adam Powell9d32d242010-03-29 16:02:07 -0700515
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200517 * Stops the animation. Contrary to {@link #forceFinished(boolean)},
518 * aborting the animating cause the scroller to move to the final x and y
519 * position
Adam Powell9d32d242010-03-29 16:02:07 -0700520 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200521 * @see #forceFinished(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 */
523 public void abortAnimation() {
Adam Powell9d32d242010-03-29 16:02:07 -0700524 mCurrX = mFinalX;
525 mCurrY = mFinalY;
526 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 }
Adam Powell9d32d242010-03-29 16:02:07 -0700528
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200530 * Extend the scroll animation. This allows a running animation to scroll
djkend663dab2009-05-08 07:07:55 -0400531 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
Adam Powell9d32d242010-03-29 16:02:07 -0700532 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 * @param extend Additional time to scroll in milliseconds.
Cyril Mottierc3129422009-05-05 18:13:48 +0200534 * @see #setFinalX(int)
535 * @see #setFinalY(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 */
537 public void extendDuration(int extend) {
Adam Powell9d32d242010-03-29 16:02:07 -0700538 int passed = timePassed();
539 mDuration = passed + extend;
Gilles Debunned348bb42010-11-15 12:19:35 -0800540 mDurationReciprocal = 1.0f / mDuration;
Adam Powell9d32d242010-03-29 16:02:07 -0700541 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200543
544 /**
545 * Returns the time elapsed since the beginning of the scrolling.
Adam Powell9d32d242010-03-29 16:02:07 -0700546 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200547 * @return The elapsed time in milliseconds.
548 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800549 public int timePassed() {
Adam Powell9d32d242010-03-29 16:02:07 -0700550 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200552
553 /**
554 * Sets the final position (X) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700555 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200556 * @param newX The new X offset as an absolute distance from the origin.
557 * @see #extendDuration(int)
558 * @see #setFinalY(int)
559 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800560 public void setFinalX(int newX) {
Adam Powell9d32d242010-03-29 16:02:07 -0700561 mFinalX = newX;
562 mDeltaX = mFinalX - mStartX;
563 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 }
565
Cyril Mottierc3129422009-05-05 18:13:48 +0200566 /**
567 * Sets the final position (Y) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700568 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200569 * @param newY The new Y offset as an absolute distance from the origin.
570 * @see #extendDuration(int)
571 * @see #setFinalX(int)
572 */
573 public void setFinalY(int newY) {
Adam Powell9d32d242010-03-29 16:02:07 -0700574 mFinalY = newY;
575 mDeltaY = mFinalY - mStartY;
576 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800577 }
Gilles Debunned348bb42010-11-15 12:19:35 -0800578
579 /**
580 * @hide
581 */
582 public boolean isScrollingInDirection(float xvel, float yvel) {
583 return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
584 Math.signum(yvel) == Math.signum(mFinalY - mStartY);
585 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586}