blob: 6ed5b7ebf4d04f24dcd9a9ba642710412ad3a322 [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
Artur Satayeved5a6ae2019-12-10 17:47:54 +000019import android.compat.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.content.Context;
Cyril Mottierc3129422009-05-05 18:13:48 +020021import android.hardware.SensorManager;
Gilles Debunned348bb42010-11-15 12:19:35 -080022import android.os.Build;
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 {
Mathew Inwood978c6e22018-08-21 15:58:55 +010064 @UnsupportedAppUsage
Alan Viveretted75e2532013-09-04 14:15:23 -070065 private final Interpolator mInterpolator;
66
Adam Powell6579b0b2010-03-25 12:21:34 -070067 private int mMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
Adam Powell9d32d242010-03-29 16:02:07 -070069 private int mStartX;
70 private int mStartY;
71 private int mFinalX;
72 private int mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073
Adam Powell9d32d242010-03-29 16:02:07 -070074 private int mMinX;
75 private int mMaxX;
76 private int mMinY;
77 private int mMaxY;
78
79 private int mCurrX;
80 private int mCurrY;
81 private long mStartTime;
Mathew Inwood978c6e22018-08-21 15:58:55 +010082 @UnsupportedAppUsage
Adam Powell9d32d242010-03-29 16:02:07 -070083 private int mDuration;
84 private float mDurationReciprocal;
85 private float mDeltaX;
86 private float mDeltaY;
Adam Powell9d32d242010-03-29 16:02:07 -070087 private boolean mFinished;
Gilles Debunned348bb42010-11-15 12:19:35 -080088 private boolean mFlywheel;
Adam Powell9d32d242010-03-29 16:02:07 -070089
Adam Powell9d32d242010-03-29 16:02:07 -070090 private float mVelocity;
Adam Powell990dfc6f2012-08-09 16:45:59 -070091 private float mCurrVelocity;
92 private int mDistance;
93
94 private float mFlingFriction = ViewConfiguration.getScrollFriction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080095
Adam Powell6579b0b2010-03-25 12:21:34 -070096 private static final int DEFAULT_DURATION = 250;
97 private static final int SCROLL_MODE = 0;
98 private static final int FLING_MODE = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099
Mathew Inwood978c6e22018-08-21 15:58:55 +0100100 @UnsupportedAppUsage
Adam Powell990dfc6f2012-08-09 16:45:59 -0700101 private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
Mathew Inwood978c6e22018-08-21 15:58:55 +0100102 @UnsupportedAppUsage
Adam Powell990dfc6f2012-08-09 16:45:59 -0700103 private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
104 private static final float START_TENSION = 0.5f;
105 private static final float END_TENSION = 1.0f;
106 private static final float P1 = START_TENSION * INFLEXION;
107 private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
108
Gilles Debunned348bb42010-11-15 12:19:35 -0800109 private static final int NB_SAMPLES = 100;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700110 private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
111 private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
Gilles Debunned348bb42010-11-15 12:19:35 -0800112
Mathew Inwood978c6e22018-08-21 15:58:55 +0100113 @UnsupportedAppUsage
Romain Guy4bede9e2010-10-11 19:36:59 -0700114 private float mDeceleration;
115 private final float mPpi;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800116
Adam Powell990dfc6f2012-08-09 16:45:59 -0700117 // A context-specific coefficient adjusted to physical values.
Mathew Inwood978c6e22018-08-21 15:58:55 +0100118 @UnsupportedAppUsage
Adam Powell990dfc6f2012-08-09 16:45:59 -0700119 private float mPhysicalCoeff;
120
Gilles Debunned348bb42010-11-15 12:19:35 -0800121 static {
122 float x_min = 0.0f;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700123 float y_min = 0.0f;
124 for (int i = 0; i < NB_SAMPLES; i++) {
125 final float alpha = (float) i / NB_SAMPLES;
126
Gilles Debunned348bb42010-11-15 12:19:35 -0800127 float x_max = 1.0f;
128 float x, tx, coef;
129 while (true) {
130 x = x_min + (x_max - x_min) / 2.0f;
131 coef = 3.0f * x * (1.0f - x);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700132 tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
133 if (Math.abs(tx - alpha) < 1E-5) break;
134 if (tx > alpha) x_max = x;
Gilles Debunned348bb42010-11-15 12:19:35 -0800135 else x_min = x;
136 }
Adam Powell990dfc6f2012-08-09 16:45:59 -0700137 SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
138
139 float y_max = 1.0f;
140 float y, dy;
141 while (true) {
142 y = y_min + (y_max - y_min) / 2.0f;
143 coef = 3.0f * y * (1.0f - y);
144 dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
145 if (Math.abs(dy - alpha) < 1E-5) break;
146 if (dy > alpha) y_max = y;
147 else y_min = y;
148 }
149 SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
Gilles Debunned348bb42010-11-15 12:19:35 -0800150 }
Adam Powell990dfc6f2012-08-09 16:45:59 -0700151 SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
Gilles Debunned348bb42010-11-15 12:19:35 -0800152 }
153
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700155 * Create a Scroller with the default duration and interpolator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800156 */
157 public Scroller(Context context) {
158 this(context, null);
159 }
160
Adam Powell637d3372010-08-25 14:37:03 -0700161 /**
162 * Create a Scroller with the specified interpolator. If the interpolator is
163 * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
164 * be in effect for apps targeting Honeycomb or newer.
165 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800166 public Scroller(Context context, Interpolator interpolator) {
167 this(context, interpolator,
168 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
169 }
170
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 /**
172 * Create a Scroller with the specified interpolator. If the interpolator is
Adam Powell637d3372010-08-25 14:37:03 -0700173 * null, the default (viscous) interpolator will be used. Specify whether or
174 * not to support progressive "flywheel" behavior in flinging.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800175 */
Gilles Debunned348bb42010-11-15 12:19:35 -0800176 public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
Adam Powell9d32d242010-03-29 16:02:07 -0700177 mFinished = true;
Alan Viveretted75e2532013-09-04 14:15:23 -0700178 if (interpolator == null) {
179 mInterpolator = new ViscousFluidInterpolator();
180 } else {
181 mInterpolator = interpolator;
182 }
Romain Guy4bede9e2010-10-11 19:36:59 -0700183 mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
184 mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
Gilles Debunned348bb42010-11-15 12:19:35 -0800185 mFlywheel = flywheel;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700186
187 mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
Romain Guy4bede9e2010-10-11 19:36:59 -0700188 }
189
190 /**
191 * The amount of friction applied to flings. The default value
192 * is {@link ViewConfiguration#getScrollFriction}.
193 *
Gilles Debunned348bb42010-11-15 12:19:35 -0800194 * @param friction A scalar dimension-less value representing the coefficient of
Romain Guy4bede9e2010-10-11 19:36:59 -0700195 * friction.
196 */
197 public final void setFriction(float friction) {
Romain Guya655c632010-10-12 10:19:25 -0700198 mDeceleration = computeDeceleration(friction);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700199 mFlingFriction = friction;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
Adam Powell9d32d242010-03-29 16:02:07 -0700201
Romain Guy4bede9e2010-10-11 19:36:59 -0700202 private float computeDeceleration(float friction) {
203 return SensorManager.GRAVITY_EARTH // g (m/s^2)
204 * 39.37f // inch/meter
205 * mPpi // pixels per inch
206 * friction;
207 }
208
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 /**
210 *
211 * Returns whether the scroller has finished scrolling.
212 *
213 * @return True if the scroller has finished scrolling, false otherwise.
214 */
215 public final boolean isFinished() {
Adam Powell9d32d242010-03-29 16:02:07 -0700216 return mFinished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 }
Adam Powell9d32d242010-03-29 16:02:07 -0700218
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 /**
220 * Force the finished field to a particular value.
Adam Powell9d32d242010-03-29 16:02:07 -0700221 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800222 * @param finished The new finished value.
223 */
224 public final void forceFinished(boolean finished) {
Adam Powell9d32d242010-03-29 16:02:07 -0700225 mFinished = finished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 }
Adam Powell9d32d242010-03-29 16:02:07 -0700227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 /**
229 * Returns how long the scroll event will take, in milliseconds.
230 *
231 * @return The duration of the scroll in milliseconds.
232 */
233 public final int getDuration() {
Adam Powell9d32d242010-03-29 16:02:07 -0700234 return mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800235 }
Adam Powell9d32d242010-03-29 16:02:07 -0700236
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700238 * Returns the current X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 *
240 * @return The new X offset as an absolute distance from the origin.
241 */
242 public final int getCurrX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700243 return mCurrX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 }
Adam Powell9d32d242010-03-29 16:02:07 -0700245
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700247 * Returns the current Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 *
249 * @return The new Y offset as an absolute distance from the origin.
250 */
251 public final int getCurrY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700252 return mCurrY;
Cary Clark278ce052009-08-31 16:08:42 -0400253 }
Adam Powell9d32d242010-03-29 16:02:07 -0700254
Cary Clark278ce052009-08-31 16:08:42 -0400255 /**
Gilles Debunne52964242010-02-24 11:05:19 -0800256 * Returns the current velocity.
Adam Powell9d32d242010-03-29 16:02:07 -0700257 *
258 * @return The original velocity less the deceleration. Result may be
259 * negative.
Gilles Debunne52964242010-02-24 11:05:19 -0800260 */
261 public float getCurrVelocity() {
Adam Powell990dfc6f2012-08-09 16:45:59 -0700262 return mMode == FLING_MODE ?
263 mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
Gilles Debunne52964242010-02-24 11:05:19 -0800264 }
265
266 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700267 * Returns the start X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 *
269 * @return The start X offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800270 */
271 public final int getStartX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700272 return mStartX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800273 }
Adam Powell9d32d242010-03-29 16:02:07 -0700274
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700276 * Returns the start Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800277 *
278 * @return The start Y offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800279 */
280 public final int getStartY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700281 return mStartY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 }
Adam Powell9d32d242010-03-29 16:02:07 -0700283
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800284 /**
285 * Returns where the scroll will end. Valid only for "fling" scrolls.
286 *
287 * @return The final X offset as an absolute distance from the origin.
288 */
289 public final int getFinalX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700290 return mFinalX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 }
Adam Powell9d32d242010-03-29 16:02:07 -0700292
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 /**
294 * Returns where the scroll will end. Valid only for "fling" scrolls.
295 *
296 * @return The final Y offset as an absolute distance from the origin.
297 */
298 public final int getFinalY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700299 return mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 }
301
302 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700303 * Call this when you want to know the new location. If it returns true,
Katie McCormick87cfad72013-02-06 18:15:13 -0800304 * the animation is not yet finished.
Adam Powell9d32d242010-03-29 16:02:07 -0700305 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 public boolean computeScrollOffset() {
Adam Powell9d32d242010-03-29 16:02:07 -0700307 if (mFinished) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 return false;
309 }
310
Adam Powell9d32d242010-03-29 16:02:07 -0700311 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
312
313 if (timePassed < mDuration) {
314 switch (mMode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 case SCROLL_MODE:
Alan Viveretted75e2532013-09-04 14:15:23 -0700316 final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
Adam Powell9d32d242010-03-29 16:02:07 -0700317 mCurrX = mStartX + Math.round(x * mDeltaX);
318 mCurrY = mStartY + Math.round(x * mDeltaY);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800319 break;
Gilles Debunne52964242010-02-24 11:05:19 -0800320 case FLING_MODE:
Gilles Debunned348bb42010-11-15 12:19:35 -0800321 final float t = (float) timePassed / mDuration;
322 final int index = (int) (NB_SAMPLES * t);
Adam Powell990dfc6f2012-08-09 16:45:59 -0700323 float distanceCoef = 1.f;
324 float velocityCoef = 0.f;
325 if (index < NB_SAMPLES) {
326 final float t_inf = (float) index / NB_SAMPLES;
327 final float t_sup = (float) (index + 1) / NB_SAMPLES;
328 final float d_inf = SPLINE_POSITION[index];
329 final float d_sup = SPLINE_POSITION[index + 1];
330 velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
331 distanceCoef = d_inf + (t - t_inf) * velocityCoef;
332 }
333
334 mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
Adam Powell9d32d242010-03-29 16:02:07 -0700335
Gilles Debunned348bb42010-11-15 12:19:35 -0800336 mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
Adam Powell9d32d242010-03-29 16:02:07 -0700337 // Pin to mMinX <= mCurrX <= mMaxX
338 mCurrX = Math.min(mCurrX, mMaxX);
339 mCurrX = Math.max(mCurrX, mMinX);
340
Gilles Debunned348bb42010-11-15 12:19:35 -0800341 mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
Adam Powell9d32d242010-03-29 16:02:07 -0700342 // Pin to mMinY <= mCurrY <= mMaxY
343 mCurrY = Math.min(mCurrY, mMaxY);
344 mCurrY = Math.max(mCurrY, mMinY);
Adam Powell1b088be2010-07-23 15:49:03 -0700345
346 if (mCurrX == mFinalX && mCurrY == mFinalY) {
347 mFinished = true;
348 }
349
Gilles Debunne52964242010-02-24 11:05:19 -0800350 break;
Adam Powell9d32d242010-03-29 16:02:07 -0700351 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800352 }
Adam Powell9d32d242010-03-29 16:02:07 -0700353 else {
354 mCurrX = mFinalX;
355 mCurrY = mFinalY;
356 mFinished = true;
357 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 return true;
359 }
Adam Powell9d32d242010-03-29 16:02:07 -0700360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 /**
362 * Start scrolling by providing a starting point and the distance to travel.
363 * The scroll will use the default value of 250 milliseconds for the
364 * duration.
365 *
366 * @param startX Starting horizontal scroll offset in pixels. Positive
367 * numbers will scroll the content to the left.
368 * @param startY Starting vertical scroll offset in pixels. Positive numbers
369 * will scroll the content up.
370 * @param dx Horizontal distance to travel. Positive numbers will scroll the
371 * content to the left.
372 * @param dy Vertical distance to travel. Positive numbers will scroll the
373 * content up.
374 */
375 public void startScroll(int startX, int startY, int dx, int dy) {
376 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
377 }
378
379 /**
Katie McCormick87cfad72013-02-06 18:15:13 -0800380 * Start scrolling by providing a starting point, the distance to travel,
381 * and the duration of the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 *
383 * @param startX Starting horizontal scroll offset in pixels. Positive
384 * numbers will scroll the content to the left.
385 * @param startY Starting vertical scroll offset in pixels. Positive numbers
386 * will scroll the content up.
387 * @param dx Horizontal distance to travel. Positive numbers will scroll the
388 * content to the left.
389 * @param dy Vertical distance to travel. Positive numbers will scroll the
390 * content up.
391 * @param duration Duration of the scroll in milliseconds.
392 */
393 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
394 mMode = SCROLL_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700395 mFinished = false;
396 mDuration = duration;
397 mStartTime = AnimationUtils.currentAnimationTimeMillis();
398 mStartX = startX;
399 mStartY = startY;
400 mFinalX = startX + dx;
401 mFinalY = startY + dy;
402 mDeltaX = dx;
403 mDeltaY = dy;
Adam Powell637d3372010-08-25 14:37:03 -0700404 mDurationReciprocal = 1.0f / (float) mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 }
406
407 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700408 * Start scrolling based on a fling gesture. The distance travelled will
409 * depend on the initial velocity of the fling.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800410 *
411 * @param startX Starting point of the scroll (X)
412 * @param startY Starting point of the scroll (Y)
413 * @param velocityX Initial velocity of the fling (X) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700414 * second.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700416 * second
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 * @param minX Minimum X 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 * @param maxX Maximum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700420 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800421 * @param minY Minimum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700422 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 * @param maxY Maximum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700424 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 */
426 public void fling(int startX, int startY, int velocityX, int velocityY,
427 int minX, int maxX, int minY, int maxY) {
Gilles Debunned348bb42010-11-15 12:19:35 -0800428 // Continue a scroll or fling in progress
429 if (mFlywheel && !mFinished) {
430 float oldVel = getCurrVelocity();
431
432 float dx = (float) (mFinalX - mStartX);
433 float dy = (float) (mFinalY - mStartY);
Neil Fuller33253a42014-10-01 11:55:10 +0100434 float hyp = (float) Math.hypot(dx, dy);
Gilles Debunned348bb42010-11-15 12:19:35 -0800435
436 float ndx = dx / hyp;
437 float ndy = dy / hyp;
438
439 float oldVelocityX = ndx * oldVel;
440 float oldVelocityY = ndy * oldVel;
441 if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
442 Math.signum(velocityY) == Math.signum(oldVelocityY)) {
443 velocityX += oldVelocityX;
444 velocityY += oldVelocityY;
445 }
446 }
447
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 mMode = FLING_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700449 mFinished = false;
Gilles Debunne52964242010-02-24 11:05:19 -0800450
Neil Fuller33253a42014-10-01 11:55:10 +0100451 float velocity = (float) Math.hypot(velocityX, velocityY);
Adam Powell9d32d242010-03-29 16:02:07 -0700452
453 mVelocity = velocity;
Adam Powell990dfc6f2012-08-09 16:45:59 -0700454 mDuration = getSplineFlingDuration(velocity);
Adam Powell9d32d242010-03-29 16:02:07 -0700455 mStartTime = AnimationUtils.currentAnimationTimeMillis();
456 mStartX = startX;
457 mStartY = startY;
458
Adam Powell637d3372010-08-25 14:37:03 -0700459 float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
460 float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
Adam Powell9d32d242010-03-29 16:02:07 -0700461
Adam Powell990dfc6f2012-08-09 16:45:59 -0700462 double totalDistance = getSplineFlingDistance(velocity);
463 mDistance = (int) (totalDistance * Math.signum(velocity));
Adam Powell9d32d242010-03-29 16:02:07 -0700464
465 mMinX = minX;
466 mMaxX = maxX;
467 mMinY = minY;
468 mMaxY = maxY;
Gilles Debunned348bb42010-11-15 12:19:35 -0800469
Adam Powell990dfc6f2012-08-09 16:45:59 -0700470 mFinalX = startX + (int) Math.round(totalDistance * coeffX);
Adam Powell9d32d242010-03-29 16:02:07 -0700471 // Pin to mMinX <= mFinalX <= mMaxX
472 mFinalX = Math.min(mFinalX, mMaxX);
473 mFinalX = Math.max(mFinalX, mMinX);
474
Adam Powell990dfc6f2012-08-09 16:45:59 -0700475 mFinalY = startY + (int) Math.round(totalDistance * coeffY);
Adam Powell9d32d242010-03-29 16:02:07 -0700476 // Pin to mMinY <= mFinalY <= mMaxY
477 mFinalY = Math.min(mFinalY, mMaxY);
478 mFinalY = Math.max(mFinalY, mMinY);
479 }
480
Adam Powell990dfc6f2012-08-09 16:45:59 -0700481 private double getSplineDeceleration(float velocity) {
482 return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
483 }
484
485 private int getSplineFlingDuration(float velocity) {
486 final double l = getSplineDeceleration(velocity);
487 final double decelMinusOne = DECELERATION_RATE - 1.0;
488 return (int) (1000.0 * Math.exp(l / decelMinusOne));
489 }
490
491 private double getSplineFlingDistance(float velocity) {
492 final double l = getSplineDeceleration(velocity);
493 final double decelMinusOne = DECELERATION_RATE - 1.0;
494 return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
495 }
496
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800497 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200498 * Stops the animation. Contrary to {@link #forceFinished(boolean)},
499 * aborting the animating cause the scroller to move to the final x and y
500 * position
Adam Powell9d32d242010-03-29 16:02:07 -0700501 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200502 * @see #forceFinished(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503 */
504 public void abortAnimation() {
Adam Powell9d32d242010-03-29 16:02:07 -0700505 mCurrX = mFinalX;
506 mCurrY = mFinalY;
507 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800508 }
Adam Powell9d32d242010-03-29 16:02:07 -0700509
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200511 * Extend the scroll animation. This allows a running animation to scroll
djkend663dab2009-05-08 07:07:55 -0400512 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
Adam Powell9d32d242010-03-29 16:02:07 -0700513 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 * @param extend Additional time to scroll in milliseconds.
Cyril Mottierc3129422009-05-05 18:13:48 +0200515 * @see #setFinalX(int)
516 * @see #setFinalY(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 */
518 public void extendDuration(int extend) {
Adam Powell9d32d242010-03-29 16:02:07 -0700519 int passed = timePassed();
520 mDuration = passed + extend;
Gilles Debunned348bb42010-11-15 12:19:35 -0800521 mDurationReciprocal = 1.0f / mDuration;
Adam Powell9d32d242010-03-29 16:02:07 -0700522 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800523 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200524
525 /**
526 * Returns the time elapsed since the beginning of the scrolling.
Adam Powell9d32d242010-03-29 16:02:07 -0700527 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200528 * @return The elapsed time in milliseconds.
529 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800530 public int timePassed() {
Adam Powell9d32d242010-03-29 16:02:07 -0700531 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800532 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200533
534 /**
535 * Sets the final position (X) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700536 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200537 * @param newX The new X offset as an absolute distance from the origin.
538 * @see #extendDuration(int)
539 * @see #setFinalY(int)
540 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800541 public void setFinalX(int newX) {
Adam Powell9d32d242010-03-29 16:02:07 -0700542 mFinalX = newX;
543 mDeltaX = mFinalX - mStartX;
544 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 }
546
Cyril Mottierc3129422009-05-05 18:13:48 +0200547 /**
548 * Sets the final position (Y) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700549 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200550 * @param newY The new Y offset as an absolute distance from the origin.
551 * @see #extendDuration(int)
552 * @see #setFinalX(int)
553 */
554 public void setFinalY(int newY) {
Adam Powell9d32d242010-03-29 16:02:07 -0700555 mFinalY = newY;
556 mDeltaY = mFinalY - mStartY;
557 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
Gilles Debunned348bb42010-11-15 12:19:35 -0800559
560 /**
561 * @hide
562 */
563 public boolean isScrollingInDirection(float xvel, float yvel) {
564 return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX) &&
565 Math.signum(yvel) == Math.signum(mFinalY - mStartY);
566 }
Alan Viveretted75e2532013-09-04 14:15:23 -0700567
Alan Viverette79a5fef2013-09-04 16:37:57 -0700568 static class ViscousFluidInterpolator implements Interpolator {
Alan Viveretted75e2532013-09-04 14:15:23 -0700569 /** Controls the viscous fluid effect (how much of it). */
570 private static final float VISCOUS_FLUID_SCALE = 8.0f;
571
572 private static final float VISCOUS_FLUID_NORMALIZE;
573 private static final float VISCOUS_FLUID_OFFSET;
574
575 static {
576
577 // must be set to 1.0 (used in viscousFluid())
578 VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);
579 // account for very small floating-point error
580 VISCOUS_FLUID_OFFSET = 1.0f - VISCOUS_FLUID_NORMALIZE * viscousFluid(1.0f);
581 }
582
583 private static float viscousFluid(float x) {
584 x *= VISCOUS_FLUID_SCALE;
585 if (x < 1.0f) {
586 x -= (1.0f - (float)Math.exp(-x));
587 } else {
588 float start = 0.36787944117f; // 1/e == exp(-1)
589 x = 1.0f - (float)Math.exp(1.0f - x);
590 x = start + x * (1.0f - start);
591 }
592 return x;
593 }
594
595 @Override
596 public float getInterpolation(float input) {
597 final float interpolated = VISCOUS_FLUID_NORMALIZE * viscousFluid(input);
Chet Haase54b9e5b2014-08-05 16:56:49 -0700598 if (interpolated > 0) {
599 return interpolated + VISCOUS_FLUID_OFFSET;
Alan Viveretted75e2532013-09-04 14:15:23 -0700600 }
Chet Haase54b9e5b2014-08-05 16:56:49 -0700601 return interpolated;
Alan Viveretted75e2532013-09-04 14:15:23 -0700602 }
603 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800604}