Scroller.java revision 4bede9e425875542976a422222510fa4056a8339
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.hardware.SensorManager; 21import 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 28 * 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. 32 */ 33public class Scroller { 34 private int mMode; 35 36 private int mStartX; 37 private int mStartY; 38 private int mFinalX; 39 private int mFinalY; 40 41 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; 61 62 private static final int DEFAULT_DURATION = 250; 63 private static final int SCROLL_MODE = 0; 64 private static final int FLING_MODE = 1; 65 66 private float mDeceleration; 67 private final float mPpi; 68 69 /** 70 * Create a Scroller with the default duration and interpolator. 71 */ 72 public Scroller(Context context) { 73 this(context, null); 74 } 75 76 /** 77 * Create a Scroller with the specified interpolator. If the interpolator is 78 * null, the default (viscous) interpolator will be used. 79 */ 80 public Scroller(Context context, Interpolator interpolator) { 81 mFinished = true; 82 mInterpolator = interpolator; 83 mPpi = context.getResources().getDisplayMetrics().density * 160.0f; 84 mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction()); 85 } 86 87 /** 88 * The amount of friction applied to flings. The default value 89 * is {@link ViewConfiguration#getScrollFriction}. 90 * 91 * @return A scalar dimensionless value representing the coefficient of 92 * friction. 93 */ 94 public final void setFriction(float friction) { 95 computeDeceleration(friction); 96 } 97 98 private float computeDeceleration(float friction) { 99 return SensorManager.GRAVITY_EARTH // g (m/s^2) 100 * 39.37f // inch/meter 101 * mPpi // pixels per inch 102 * friction; 103 } 104 105 /** 106 * 107 * Returns whether the scroller has finished scrolling. 108 * 109 * @return True if the scroller has finished scrolling, false otherwise. 110 */ 111 public final boolean isFinished() { 112 return mFinished; 113 } 114 115 /** 116 * Force the finished field to a particular value. 117 * 118 * @param finished The new finished value. 119 */ 120 public final void forceFinished(boolean finished) { 121 mFinished = finished; 122 } 123 124 /** 125 * Returns how long the scroll event will take, in milliseconds. 126 * 127 * @return The duration of the scroll in milliseconds. 128 */ 129 public final int getDuration() { 130 return mDuration; 131 } 132 133 /** 134 * Returns the current X offset in the scroll. 135 * 136 * @return The new X offset as an absolute distance from the origin. 137 */ 138 public final int getCurrX() { 139 return mCurrX; 140 } 141 142 /** 143 * Returns the current Y offset in the scroll. 144 * 145 * @return The new Y offset as an absolute distance from the origin. 146 */ 147 public final int getCurrY() { 148 return mCurrY; 149 } 150 151 /** 152 * @hide 153 * Returns the current velocity. 154 * 155 * @return The original velocity less the deceleration. Result may be 156 * negative. 157 */ 158 public float getCurrVelocity() { 159 return mVelocity - mDeceleration * timePassed() / 2000.0f; 160 } 161 162 /** 163 * Returns the start X offset in the scroll. 164 * 165 * @return The start X offset as an absolute distance from the origin. 166 */ 167 public final int getStartX() { 168 return mStartX; 169 } 170 171 /** 172 * Returns the start Y offset in the scroll. 173 * 174 * @return The start Y offset as an absolute distance from the origin. 175 */ 176 public final int getStartY() { 177 return mStartY; 178 } 179 180 /** 181 * Returns where the scroll will end. Valid only for "fling" scrolls. 182 * 183 * @return The final X offset as an absolute distance from the origin. 184 */ 185 public final int getFinalX() { 186 return mFinalX; 187 } 188 189 /** 190 * Returns where the scroll will end. Valid only for "fling" scrolls. 191 * 192 * @return The final Y offset as an absolute distance from the origin. 193 */ 194 public final int getFinalY() { 195 return mFinalY; 196 } 197 198 /** 199 * Call this when you want to know the new location. If it returns true, 200 * the animation is not yet finished. loc will be altered to provide the 201 * new location. 202 */ 203 public boolean computeScrollOffset() { 204 if (mFinished) { 205 return false; 206 } 207 208 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 209 210 if (timePassed < mDuration) { 211 switch (mMode) { 212 case SCROLL_MODE: 213 float x = (float)timePassed * mDurationReciprocal; 214 215 if (mInterpolator == null) 216 x = viscousFluid(x); 217 else 218 x = mInterpolator.getInterpolation(x); 219 220 mCurrX = mStartX + Math.round(x * mDeltaX); 221 mCurrY = mStartY + Math.round(x * mDeltaY); 222 break; 223 case FLING_MODE: 224 float timePassedSeconds = timePassed / 1000.0f; 225 float distance = (mVelocity * timePassedSeconds) 226 - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f); 227 228 mCurrX = mStartX + Math.round(distance * mCoeffX); 229 // Pin to mMinX <= mCurrX <= mMaxX 230 mCurrX = Math.min(mCurrX, mMaxX); 231 mCurrX = Math.max(mCurrX, mMinX); 232 233 mCurrY = mStartY + Math.round(distance * mCoeffY); 234 // Pin to mMinY <= mCurrY <= mMaxY 235 mCurrY = Math.min(mCurrY, mMaxY); 236 mCurrY = Math.max(mCurrY, mMinY); 237 238 if (mCurrX == mFinalX && mCurrY == mFinalY) { 239 mFinished = true; 240 } 241 242 break; 243 } 244 } 245 else { 246 mCurrX = mFinalX; 247 mCurrY = mFinalY; 248 mFinished = true; 249 } 250 return true; 251 } 252 253 /** 254 * Start scrolling by providing a starting point and the distance to travel. 255 * The scroll will use the default value of 250 milliseconds for the 256 * duration. 257 * 258 * @param startX Starting horizontal scroll offset in pixels. Positive 259 * numbers will scroll the content to the left. 260 * @param startY Starting vertical scroll offset in pixels. Positive numbers 261 * will scroll the content up. 262 * @param dx Horizontal distance to travel. Positive numbers will scroll the 263 * content to the left. 264 * @param dy Vertical distance to travel. Positive numbers will scroll the 265 * content up. 266 */ 267 public void startScroll(int startX, int startY, int dx, int dy) { 268 startScroll(startX, startY, dx, dy, DEFAULT_DURATION); 269 } 270 271 /** 272 * Start scrolling by providing a starting point and the distance to travel. 273 * 274 * @param startX Starting horizontal scroll offset in pixels. Positive 275 * numbers will scroll the content to the left. 276 * @param startY Starting vertical scroll offset in pixels. Positive numbers 277 * will scroll the content up. 278 * @param dx Horizontal distance to travel. Positive numbers will scroll the 279 * content to the left. 280 * @param dy Vertical distance to travel. Positive numbers will scroll the 281 * content up. 282 * @param duration Duration of the scroll in milliseconds. 283 */ 284 public void startScroll(int startX, int startY, int dx, int dy, int duration) { 285 mMode = SCROLL_MODE; 286 mFinished = false; 287 mDuration = duration; 288 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 289 mStartX = startX; 290 mStartY = startY; 291 mFinalX = startX + dx; 292 mFinalY = startY + dy; 293 mDeltaX = dx; 294 mDeltaY = dy; 295 mDurationReciprocal = 1.0f / (float) mDuration; 296 // This controls the viscous fluid effect (how much of it) 297 mViscousFluidScale = 8.0f; 298 // must be set to 1.0 (used in viscousFluid()) 299 mViscousFluidNormalize = 1.0f; 300 mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); 301 } 302 303 /** 304 * Start scrolling based on a fling gesture. The distance travelled will 305 * depend on the initial velocity of the fling. 306 * 307 * @param startX Starting point of the scroll (X) 308 * @param startY Starting point of the scroll (Y) 309 * @param velocityX Initial velocity of the fling (X) measured in pixels per 310 * second. 311 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 312 * second 313 * @param minX Minimum X value. The scroller will not scroll past this 314 * point. 315 * @param maxX Maximum X value. The scroller will not scroll past this 316 * point. 317 * @param minY Minimum Y value. The scroller will not scroll past this 318 * point. 319 * @param maxY Maximum Y value. The scroller will not scroll past this 320 * point. 321 */ 322 public void fling(int startX, int startY, int velocityX, int velocityY, 323 int minX, int maxX, int minY, int maxY) { 324 mMode = FLING_MODE; 325 mFinished = false; 326 327 float velocity = (float)Math.hypot(velocityX, velocityY); 328 329 mVelocity = velocity; 330 mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in 331 // milliseconds 332 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 333 mStartX = startX; 334 mStartY = startY; 335 336 mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 337 mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity; 338 339 int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration)); 340 341 mMinX = minX; 342 mMaxX = maxX; 343 mMinY = minY; 344 mMaxY = maxY; 345 346 347 mFinalX = startX + Math.round(totalDistance * mCoeffX); 348 // Pin to mMinX <= mFinalX <= mMaxX 349 mFinalX = Math.min(mFinalX, mMaxX); 350 mFinalX = Math.max(mFinalX, mMinX); 351 352 mFinalY = startY + Math.round(totalDistance * mCoeffY); 353 // Pin to mMinY <= mFinalY <= mMaxY 354 mFinalY = Math.min(mFinalY, mMaxY); 355 mFinalY = Math.max(mFinalY, mMinY); 356 } 357 358 359 360 private float viscousFluid(float x) 361 { 362 x *= mViscousFluidScale; 363 if (x < 1.0f) { 364 x -= (1.0f - (float)Math.exp(-x)); 365 } else { 366 float start = 0.36787944117f; // 1/e == exp(-1) 367 x = 1.0f - (float)Math.exp(1.0f - x); 368 x = start + x * (1.0f - start); 369 } 370 x *= mViscousFluidNormalize; 371 return x; 372 } 373 374 /** 375 * Stops the animation. Contrary to {@link #forceFinished(boolean)}, 376 * aborting the animating cause the scroller to move to the final x and y 377 * position 378 * 379 * @see #forceFinished(boolean) 380 */ 381 public void abortAnimation() { 382 mCurrX = mFinalX; 383 mCurrY = mFinalY; 384 mFinished = true; 385 } 386 387 /** 388 * Extend the scroll animation. This allows a running animation to scroll 389 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}. 390 * 391 * @param extend Additional time to scroll in milliseconds. 392 * @see #setFinalX(int) 393 * @see #setFinalY(int) 394 */ 395 public void extendDuration(int extend) { 396 int passed = timePassed(); 397 mDuration = passed + extend; 398 mDurationReciprocal = 1.0f / (float)mDuration; 399 mFinished = false; 400 } 401 402 /** 403 * Returns the time elapsed since the beginning of the scrolling. 404 * 405 * @return The elapsed time in milliseconds. 406 */ 407 public int timePassed() { 408 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 409 } 410 411 /** 412 * Sets the final position (X) for this scroller. 413 * 414 * @param newX The new X offset as an absolute distance from the origin. 415 * @see #extendDuration(int) 416 * @see #setFinalY(int) 417 */ 418 public void setFinalX(int newX) { 419 mFinalX = newX; 420 mDeltaX = mFinalX - mStartX; 421 mFinished = false; 422 } 423 424 /** 425 * Sets the final position (Y) for this scroller. 426 * 427 * @param newY The new Y offset as an absolute distance from the origin. 428 * @see #extendDuration(int) 429 * @see #setFinalX(int) 430 */ 431 public void setFinalY(int newY) { 432 mFinalY = newY; 433 mDeltaY = mFinalY - mStartY; 434 mFinished = false; 435 } 436} 437