Scroller.java revision c312942b773449f5c6a8a887052aec847ec73b2c
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 final float mDeceleration; 67 68 /** 69 * Create a Scroller with the default duration and interpolator. 70 */ 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) { 80 mFinished = true; 81 mInterpolator = interpolator; 82 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(); 87 } 88 89 /** 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() { 96 return mFinished; 97 } 98 99 /** 100 * Force the finished field to a particular value. 101 * 102 * @param finished The new finished value. 103 */ 104 public final void forceFinished(boolean finished) { 105 mFinished = finished; 106 } 107 108 /** 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() { 114 return mDuration; 115 } 116 117 /** 118 * Returns the current X offset in the scroll. 119 * 120 * @return The new X offset as an absolute distance from the origin. 121 */ 122 public final int getCurrX() { 123 return mCurrX; 124 } 125 126 /** 127 * Returns the current Y offset in the scroll. 128 * 129 * @return The new Y offset as an absolute distance from the origin. 130 */ 131 public final int getCurrY() { 132 return mCurrY; 133 } 134 135 /** 136 * Returns the start X offset in the scroll. 137 * 138 * @return The start X offset as an absolute distance from the origin. 139 */ 140 public final int getStartX() { 141 return mStartX; 142 } 143 144 /** 145 * Returns the start Y offset in the scroll. 146 * 147 * @return The start Y offset as an absolute distance from the origin. 148 */ 149 public final int getStartY() { 150 return mStartY; 151 } 152 153 /** 154 * Returns where the scroll will end. Valid only for "fling" scrolls. 155 * 156 * @return The final X offset as an absolute distance from the origin. 157 */ 158 public final int getFinalX() { 159 return mFinalX; 160 } 161 162 /** 163 * Returns where the scroll will end. Valid only for "fling" scrolls. 164 * 165 * @return The final Y offset as an absolute distance from the origin. 166 */ 167 public final int getFinalY() { 168 return mFinalY; 169 } 170 171 /** 172 * Call this when you want to know the new location. If it returns true, 173 * the animation is not yet finished. loc will be altered to provide the 174 * new location. 175 */ 176 public boolean computeScrollOffset() { 177 if (mFinished) { 178 return false; 179 } 180 181 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 182 183 if (timePassed < mDuration) { 184 switch (mMode) { 185 case SCROLL_MODE: 186 float x = (float)timePassed * mDurationReciprocal; 187 188 if (mInterpolator == null) 189 x = viscousFluid(x); 190 else 191 x = mInterpolator.getInterpolation(x); 192 193 mCurrX = mStartX + Math.round(x * mDeltaX); 194 mCurrY = mStartY + Math.round(x * mDeltaY); 195 if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) { 196 mFinished = true; 197 } 198 break; 199 case FLING_MODE: 200 float timePassedSeconds = timePassed / 1000.0f; 201 float distance = (mVelocity * timePassedSeconds) 202 - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f); 203 204 mCurrX = mStartX + Math.round(distance * mCoeffX); 205 // Pin to mMinX <= mCurrX <= mMaxX 206 mCurrX = Math.min(mCurrX, mMaxX); 207 mCurrX = Math.max(mCurrX, mMinX); 208 209 mCurrY = mStartY + Math.round(distance * mCoeffY); 210 // Pin to mMinY <= mCurrY <= mMaxY 211 mCurrY = Math.min(mCurrY, mMaxY); 212 mCurrY = Math.max(mCurrY, mMinY); 213 214 if (mCurrX == mFinalX && mCurrY == mFinalY) { 215 mFinished = true; 216 } 217 218 break; 219 } 220 } 221 else { 222 mCurrX = mFinalX; 223 mCurrY = mFinalY; 224 mFinished = true; 225 } 226 return true; 227 } 228 229 /** 230 * Start scrolling by providing a starting point and the distance to travel. 231 * The scroll will use the default value of 250 milliseconds for the 232 * duration. 233 * 234 * @param startX Starting horizontal scroll offset in pixels. Positive 235 * numbers will scroll the content to the left. 236 * @param startY Starting vertical scroll offset in pixels. Positive numbers 237 * will scroll the content up. 238 * @param dx Horizontal distance to travel. Positive numbers will scroll the 239 * content to the left. 240 * @param dy Vertical distance to travel. Positive numbers will scroll the 241 * content up. 242 */ 243 public void startScroll(int startX, int startY, int dx, int dy) { 244 startScroll(startX, startY, dx, dy, DEFAULT_DURATION); 245 } 246 247 /** 248 * Start scrolling by providing a starting point and the distance to travel. 249 * 250 * @param startX Starting horizontal scroll offset in pixels. Positive 251 * numbers will scroll the content to the left. 252 * @param startY Starting vertical scroll offset in pixels. Positive numbers 253 * will scroll the content up. 254 * @param dx Horizontal distance to travel. Positive numbers will scroll the 255 * content to the left. 256 * @param dy Vertical distance to travel. Positive numbers will scroll the 257 * content up. 258 * @param duration Duration of the scroll in milliseconds. 259 */ 260 public void startScroll(int startX, int startY, int dx, int dy, int duration) { 261 mMode = SCROLL_MODE; 262 mFinished = false; 263 mDuration = duration; 264 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 265 mStartX = startX; 266 mStartY = startY; 267 mFinalX = startX + dx; 268 mFinalY = startY + dy; 269 mDeltaX = dx; 270 mDeltaY = dy; 271 mDurationReciprocal = 1.0f / (float) mDuration; 272 // This controls the viscous fluid effect (how much of it) 273 mViscousFluidScale = 8.0f; 274 // must be set to 1.0 (used in viscousFluid()) 275 mViscousFluidNormalize = 1.0f; 276 mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); 277 } 278 279 /** 280 * Start scrolling based on a fling gesture. The distance travelled will 281 * depend on the initial velocity of the fling. 282 * 283 * @param startX Starting point of the scroll (X) 284 * @param startY Starting point of the scroll (Y) 285 * @param velocityX Initial velocity of the fling (X) measured in pixels per 286 * second. 287 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 288 * second 289 * @param minX Minimum X value. The scroller will not scroll past this 290 * point. 291 * @param maxX Maximum X value. The scroller will not scroll past this 292 * point. 293 * @param minY Minimum Y value. The scroller will not scroll past this 294 * point. 295 * @param maxY Maximum Y value. The scroller will not scroll past this 296 * point. 297 */ 298 public void fling(int startX, int startY, int velocityX, int velocityY, 299 int minX, int maxX, int minY, int maxY) { 300 mMode = FLING_MODE; 301 mFinished = false; 302 303 float velocity = (float)Math.hypot(velocityX, velocityY); 304 305 mVelocity = velocity; 306 mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in 307 // milliseconds 308 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 309 mStartX = startX; 310 mStartY = startY; 311 312 mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 313 mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity; 314 315 int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration)); 316 317 mMinX = minX; 318 mMaxX = maxX; 319 mMinY = minY; 320 mMaxY = maxY; 321 322 323 mFinalX = startX + Math.round(totalDistance * mCoeffX); 324 // Pin to mMinX <= mFinalX <= mMaxX 325 mFinalX = Math.min(mFinalX, mMaxX); 326 mFinalX = Math.max(mFinalX, mMinX); 327 328 mFinalY = startY + Math.round(totalDistance * mCoeffY); 329 // Pin to mMinY <= mFinalY <= mMaxY 330 mFinalY = Math.min(mFinalY, mMaxY); 331 mFinalY = Math.max(mFinalY, mMinY); 332 } 333 334 335 336 private float viscousFluid(float x) 337 { 338 x *= mViscousFluidScale; 339 if (x < 1.0f) { 340 x -= (1.0f - (float)Math.exp(-x)); 341 } else { 342 float start = 0.36787944117f; // 1/e == exp(-1) 343 x = 1.0f - (float)Math.exp(1.0f - x); 344 x = start + x * (1.0f - start); 345 } 346 x *= mViscousFluidNormalize; 347 return x; 348 } 349 350 /** 351 * Stops the animation. Contrary to {@link #forceFinished(boolean)}, 352 * aborting the animating cause the scroller to move to the final x and y 353 * position 354 * 355 * @see #forceFinished(boolean) 356 */ 357 public void abortAnimation() { 358 mCurrX = mFinalX; 359 mCurrY = mFinalY; 360 mFinished = true; 361 } 362 363 /** 364 * Extend the scroll animation. This allows a running animation to scroll 365 * further and longer, when used with {@link #setFinalX()} or {@link setFinalY()}. 366 * 367 * @param extend Additional time to scroll in milliseconds. 368 * @see #setFinalX(int) 369 * @see #setFinalY(int) 370 */ 371 public void extendDuration(int extend) { 372 int passed = timePassed(); 373 mDuration = passed + extend; 374 mDurationReciprocal = 1.0f / (float)mDuration; 375 mFinished = false; 376 } 377 378 /** 379 * Returns the time elapsed since the beginning of the scrolling. 380 * 381 * @return The elapsed time in milliseconds. 382 */ 383 public int timePassed() { 384 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 385 } 386 387 /** 388 * Sets the final position (X) for this scroller. 389 * 390 * @param newX The new X offset as an absolute distance from the origin. 391 * @see #extendDuration(int) 392 * @see #setFinalY(int) 393 */ 394 public void setFinalX(int newX) { 395 mFinalX = newX; 396 mDeltaX = mFinalX - mStartX; 397 mFinished = false; 398 } 399 400 /** 401 * Sets the final position (Y) for this scroller. 402 * 403 * @param newY The new Y offset as an absolute distance from the origin. 404 * @see #extendDuration(int) 405 * @see #setFinalX(int) 406 */ 407 public void setFinalY(int newY) { 408 mFinalY = newY; 409 mDeltaY = mFinalY - mStartY; 410 mFinished = false; 411 } 412} 413