blob: 11dab02f68e885dfe916458cd53dc88748dacdf8 [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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.view.ViewConfiguration;
22import android.view.animation.AnimationUtils;
23import android.view.animation.Interpolator;
24
25
26/**
27 * This class encapsulates scrolling. The duration of the scroll
Adam Powell9d32d242010-03-29 16:02:07 -070028 * can be passed in the constructor and specifies the maximum time that
29 * the scrolling animation should take. Past this time, the scrolling is
30 * automatically moved to its final stage and computeScrollOffset()
31 * will always return false to indicate that scrolling is over.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032 */
33public class Scroller {
Adam Powell6579b0b2010-03-25 12:21:34 -070034 private int mMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
Adam Powell9d32d242010-03-29 16:02:07 -070036 private int mStartX;
37 private int mStartY;
38 private int mFinalX;
39 private int mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
Adam Powell9d32d242010-03-29 16:02:07 -070041 private int mMinX;
42 private int mMaxX;
43 private int mMinY;
44 private int mMaxY;
45
46 private int mCurrX;
47 private int mCurrY;
48 private long mStartTime;
49 private int mDuration;
50 private float mDurationReciprocal;
51 private float mDeltaX;
52 private float mDeltaY;
53 private float mViscousFluidScale;
54 private float mViscousFluidNormalize;
55 private boolean mFinished;
56 private Interpolator mInterpolator;
57
58 private float mCoeffX = 0.0f;
59 private float mCoeffY = 1.0f;
60 private float mVelocity;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
Adam Powell6579b0b2010-03-25 12:21:34 -070062 private static final int DEFAULT_DURATION = 250;
63 private static final int SCROLL_MODE = 0;
64 private static final int FLING_MODE = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
Adam Powell9d32d242010-03-29 16:02:07 -070066 private final float mDeceleration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
68 /**
Adam Powell9d32d242010-03-29 16:02:07 -070069 * Create a Scroller with the default duration and interpolator.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 */
71 public Scroller(Context context) {
72 this(context, null);
73 }
74
75 /**
76 * Create a Scroller with the specified interpolator. If the interpolator is
77 * null, the default (viscous) interpolator will be used.
78 */
79 public Scroller(Context context, Interpolator interpolator) {
Adam Powell9d32d242010-03-29 16:02:07 -070080 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 mInterpolator = interpolator;
Adam Powell9d32d242010-03-29 16:02:07 -070082 float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
83 mDeceleration = SensorManager.GRAVITY_EARTH // g (m/s^2)
84 * 39.37f // inch/meter
85 * ppi // pixels per inch
86 * ViewConfiguration.getScrollFriction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 }
Adam Powell9d32d242010-03-29 16:02:07 -070088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 /**
90 *
91 * Returns whether the scroller has finished scrolling.
92 *
93 * @return True if the scroller has finished scrolling, false otherwise.
94 */
95 public final boolean isFinished() {
Adam Powell9d32d242010-03-29 16:02:07 -070096 return mFinished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080097 }
Adam Powell9d32d242010-03-29 16:02:07 -070098
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 /**
100 * Force the finished field to a particular value.
Adam Powell9d32d242010-03-29 16:02:07 -0700101 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800102 * @param finished The new finished value.
103 */
104 public final void forceFinished(boolean finished) {
Adam Powell9d32d242010-03-29 16:02:07 -0700105 mFinished = finished;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800106 }
Adam Powell9d32d242010-03-29 16:02:07 -0700107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108 /**
109 * Returns how long the scroll event will take, in milliseconds.
110 *
111 * @return The duration of the scroll in milliseconds.
112 */
113 public final int getDuration() {
Adam Powell9d32d242010-03-29 16:02:07 -0700114 return mDuration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800115 }
Adam Powell9d32d242010-03-29 16:02:07 -0700116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700118 * Returns the current X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 *
120 * @return The new X offset as an absolute distance from the origin.
121 */
122 public final int getCurrX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700123 return mCurrX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 }
Adam Powell9d32d242010-03-29 16:02:07 -0700125
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700127 * Returns the current Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 *
129 * @return The new Y offset as an absolute distance from the origin.
130 */
131 public final int getCurrY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700132 return mCurrY;
Cary Clark278ce052009-08-31 16:08:42 -0400133 }
Adam Powell9d32d242010-03-29 16:02:07 -0700134
Cary Clark278ce052009-08-31 16:08:42 -0400135 /**
Gilles Debunne52964242010-02-24 11:05:19 -0800136 * @hide
137 * Returns the current velocity.
Adam Powell9d32d242010-03-29 16:02:07 -0700138 *
139 * @return The original velocity less the deceleration. Result may be
140 * negative.
Gilles Debunne52964242010-02-24 11:05:19 -0800141 */
142 public float getCurrVelocity() {
Adam Powell9d32d242010-03-29 16:02:07 -0700143 return mVelocity - mDeceleration * timePassed() / 2000.0f;
Gilles Debunne52964242010-02-24 11:05:19 -0800144 }
145
146 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700147 * Returns the start X offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 *
149 * @return The start X offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800150 */
151 public final int getStartX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700152 return mStartX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 }
Adam Powell9d32d242010-03-29 16:02:07 -0700154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700156 * Returns the start Y offset in the scroll.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 *
158 * @return The start Y offset as an absolute distance from the origin.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 */
160 public final int getStartY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700161 return mStartY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 }
Adam Powell9d32d242010-03-29 16:02:07 -0700163
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 /**
165 * Returns where the scroll will end. Valid only for "fling" scrolls.
166 *
167 * @return The final X offset as an absolute distance from the origin.
168 */
169 public final int getFinalX() {
Adam Powell9d32d242010-03-29 16:02:07 -0700170 return mFinalX;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 }
Adam Powell9d32d242010-03-29 16:02:07 -0700172
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 /**
174 * Returns where the scroll will end. Valid only for "fling" scrolls.
175 *
176 * @return The final Y offset as an absolute distance from the origin.
177 */
178 public final int getFinalY() {
Adam Powell9d32d242010-03-29 16:02:07 -0700179 return mFinalY;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
181
182 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700183 * Call this when you want to know the new location. If it returns true,
184 * the animation is not yet finished. loc will be altered to provide the
185 * new location.
186 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 public boolean computeScrollOffset() {
Adam Powell9d32d242010-03-29 16:02:07 -0700188 if (mFinished) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800189 return false;
190 }
191
Adam Powell9d32d242010-03-29 16:02:07 -0700192 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
193
194 if (timePassed < mDuration) {
195 switch (mMode) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 case SCROLL_MODE:
Adam Powell9d32d242010-03-29 16:02:07 -0700197 float x = (float)timePassed * mDurationReciprocal;
198
199 if (mInterpolator == null)
200 x = viscousFluid(x);
201 else
202 x = mInterpolator.getInterpolation(x);
203
204 mCurrX = mStartX + Math.round(x * mDeltaX);
205 mCurrY = mStartY + Math.round(x * mDeltaY);
206 if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) {
207 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 break;
Gilles Debunne52964242010-02-24 11:05:19 -0800210 case FLING_MODE:
Adam Powell9d32d242010-03-29 16:02:07 -0700211 float timePassedSeconds = timePassed / 1000.0f;
212 float distance = (mVelocity * timePassedSeconds)
213 - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
214
215 mCurrX = mStartX + Math.round(distance * mCoeffX);
216 // Pin to mMinX <= mCurrX <= mMaxX
217 mCurrX = Math.min(mCurrX, mMaxX);
218 mCurrX = Math.max(mCurrX, mMinX);
219
220 mCurrY = mStartY + Math.round(distance * mCoeffY);
221 // Pin to mMinY <= mCurrY <= mMaxY
222 mCurrY = Math.min(mCurrY, mMaxY);
223 mCurrY = Math.max(mCurrY, mMinY);
Gilles Debunne52964242010-02-24 11:05:19 -0800224
Adam Powell9d32d242010-03-29 16:02:07 -0700225 if (mCurrX == mFinalX && mCurrY == mFinalY) {
226 mFinished = true;
Gilles Debunne52964242010-02-24 11:05:19 -0800227 }
Adam Powell9d32d242010-03-29 16:02:07 -0700228
Gilles Debunne52964242010-02-24 11:05:19 -0800229 break;
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 else {
233 mCurrX = mFinalX;
234 mCurrY = mFinalY;
235 mFinished = true;
236 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 return true;
238 }
Adam Powell9d32d242010-03-29 16:02:07 -0700239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 /**
241 * Start scrolling by providing a starting point and the distance to travel.
242 * The scroll will use the default value of 250 milliseconds for the
243 * duration.
244 *
245 * @param startX Starting horizontal scroll offset in pixels. Positive
246 * numbers will scroll the content to the left.
247 * @param startY Starting vertical scroll offset in pixels. Positive numbers
248 * will scroll the content up.
249 * @param dx Horizontal distance to travel. Positive numbers will scroll the
250 * content to the left.
251 * @param dy Vertical distance to travel. Positive numbers will scroll the
252 * content up.
253 */
254 public void startScroll(int startX, int startY, int dx, int dy) {
255 startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
256 }
257
258 /**
259 * Start scrolling by providing a starting point and the distance to travel.
260 *
261 * @param startX Starting horizontal scroll offset in pixels. Positive
262 * numbers will scroll the content to the left.
263 * @param startY Starting vertical scroll offset in pixels. Positive numbers
264 * will scroll the content up.
265 * @param dx Horizontal distance to travel. Positive numbers will scroll the
266 * content to the left.
267 * @param dy Vertical distance to travel. Positive numbers will scroll the
268 * content up.
269 * @param duration Duration of the scroll in milliseconds.
270 */
271 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
272 mMode = SCROLL_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700273 mFinished = false;
274 mDuration = duration;
275 mStartTime = AnimationUtils.currentAnimationTimeMillis();
276 mStartX = startX;
277 mStartY = startY;
278 mFinalX = startX + dx;
279 mFinalY = startY + dy;
280 mDeltaX = dx;
281 mDeltaY = dy;
282 mDurationReciprocal = 1.0f / (float) mDuration;
283 // This controls the viscous fluid effect (how much of it)
284 mViscousFluidScale = 8.0f;
285 // must be set to 1.0 (used in viscousFluid())
286 mViscousFluidNormalize = 1.0f;
287 mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800288 }
289
290 /**
Adam Powell9d32d242010-03-29 16:02:07 -0700291 * Start scrolling based on a fling gesture. The distance travelled will
292 * depend on the initial velocity of the fling.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 *
294 * @param startX Starting point of the scroll (X)
295 * @param startY Starting point of the scroll (Y)
296 * @param velocityX Initial velocity of the fling (X) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700297 * second.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800298 * @param velocityY Initial velocity of the fling (Y) measured in pixels per
Adam Powell9d32d242010-03-29 16:02:07 -0700299 * second
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 * @param minX Minimum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700301 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 * @param maxX Maximum X value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700303 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 * @param minY Minimum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700305 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 * @param maxY Maximum Y value. The scroller will not scroll past this
Adam Powell9d32d242010-03-29 16:02:07 -0700307 * point.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 */
309 public void fling(int startX, int startY, int velocityX, int velocityY,
310 int minX, int maxX, int minY, int maxY) {
311 mMode = FLING_MODE;
Adam Powell9d32d242010-03-29 16:02:07 -0700312 mFinished = false;
Gilles Debunne52964242010-02-24 11:05:19 -0800313
Adam Powell9d32d242010-03-29 16:02:07 -0700314 float velocity = (float)Math.hypot(velocityX, velocityY);
315
316 mVelocity = velocity;
317 mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
318 // milliseconds
319 mStartTime = AnimationUtils.currentAnimationTimeMillis();
320 mStartX = startX;
321 mStartY = startY;
322
323 mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity;
324 mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
325
326 int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
327
328 mMinX = minX;
329 mMaxX = maxX;
330 mMinY = minY;
331 mMaxY = maxY;
332
333
334 mFinalX = startX + Math.round(totalDistance * mCoeffX);
335 // Pin to mMinX <= mFinalX <= mMaxX
336 mFinalX = Math.min(mFinalX, mMaxX);
337 mFinalX = Math.max(mFinalX, mMinX);
338
339 mFinalY = startY + Math.round(totalDistance * mCoeffY);
340 // Pin to mMinY <= mFinalY <= mMaxY
341 mFinalY = Math.min(mFinalY, mMaxY);
342 mFinalY = Math.max(mFinalY, mMinY);
343 }
344
345
346
347 private float viscousFluid(float x)
348 {
349 x *= mViscousFluidScale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800350 if (x < 1.0f) {
351 x -= (1.0f - (float)Math.exp(-x));
352 } else {
353 float start = 0.36787944117f; // 1/e == exp(-1)
354 x = 1.0f - (float)Math.exp(1.0f - x);
355 x = start + x * (1.0f - start);
356 }
Adam Powell9d32d242010-03-29 16:02:07 -0700357 x *= mViscousFluidNormalize;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800358 return x;
359 }
Adam Powell9d32d242010-03-29 16:02:07 -0700360
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800361 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200362 * Stops the animation. Contrary to {@link #forceFinished(boolean)},
363 * aborting the animating cause the scroller to move to the final x and y
364 * position
Adam Powell9d32d242010-03-29 16:02:07 -0700365 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200366 * @see #forceFinished(boolean)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800367 */
368 public void abortAnimation() {
Adam Powell9d32d242010-03-29 16:02:07 -0700369 mCurrX = mFinalX;
370 mCurrY = mFinalY;
371 mFinished = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800372 }
Adam Powell9d32d242010-03-29 16:02:07 -0700373
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374 /**
Cyril Mottierc3129422009-05-05 18:13:48 +0200375 * Extend the scroll animation. This allows a running animation to scroll
djkend663dab2009-05-08 07:07:55 -0400376 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
Adam Powell9d32d242010-03-29 16:02:07 -0700377 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800378 * @param extend Additional time to scroll in milliseconds.
Cyril Mottierc3129422009-05-05 18:13:48 +0200379 * @see #setFinalX(int)
380 * @see #setFinalY(int)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800381 */
382 public void extendDuration(int extend) {
Adam Powell9d32d242010-03-29 16:02:07 -0700383 int passed = timePassed();
384 mDuration = passed + extend;
385 mDurationReciprocal = 1.0f / (float)mDuration;
386 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200388
389 /**
390 * Returns the time elapsed since the beginning of the scrolling.
Adam Powell9d32d242010-03-29 16:02:07 -0700391 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200392 * @return The elapsed time in milliseconds.
393 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800394 public int timePassed() {
Adam Powell9d32d242010-03-29 16:02:07 -0700395 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 }
Cyril Mottierc3129422009-05-05 18:13:48 +0200397
398 /**
399 * Sets the final position (X) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700400 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200401 * @param newX The new X offset as an absolute distance from the origin.
402 * @see #extendDuration(int)
403 * @see #setFinalY(int)
404 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 public void setFinalX(int newX) {
Adam Powell9d32d242010-03-29 16:02:07 -0700406 mFinalX = newX;
407 mDeltaX = mFinalX - mStartX;
408 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409 }
410
Cyril Mottierc3129422009-05-05 18:13:48 +0200411 /**
412 * Sets the final position (Y) for this scroller.
Adam Powell9d32d242010-03-29 16:02:07 -0700413 *
Cyril Mottierc3129422009-05-05 18:13:48 +0200414 * @param newY The new Y offset as an absolute distance from the origin.
415 * @see #extendDuration(int)
416 * @see #setFinalX(int)
417 */
418 public void setFinalY(int newY) {
Adam Powell9d32d242010-03-29 16:02:07 -0700419 mFinalY = newY;
420 mDeltaY = mFinalY - mStartY;
421 mFinished = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800422 }
423}