Scroller.java revision 278ce05b52a47a09d2177435b533f4ecb1b9c4da
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 * @hide 137 * Returns the current velocity. 138 * 139 * @return The original velocity less the deceleration. Result may be 140 * negative. 141 */ 142 public float getCurrVelocity() { 143 return mVelocity - mDeceleration * timePassed() / 2000.0f; 144 } 145 146 /** 147 * Returns the start X offset in the scroll. 148 * 149 * @return The start X offset as an absolute distance from the origin. 150 */ 151 public final int getStartX() { 152 return mStartX; 153 } 154 155 /** 156 * Returns the start Y offset in the scroll. 157 * 158 * @return The start Y offset as an absolute distance from the origin. 159 */ 160 public final int getStartY() { 161 return mStartY; 162 } 163 164 /** 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() { 170 return mFinalX; 171 } 172 173 /** 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() { 179 return mFinalY; 180 } 181 182 /** 183 * 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 */ 187 public boolean computeScrollOffset() { 188 if (mFinished) { 189 return false; 190 } 191 192 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 193 194 if (timePassed < mDuration) { 195 switch (mMode) { 196 case SCROLL_MODE: 197 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; 208 } 209 break; 210 case FLING_MODE: 211 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); 224 225 if (mCurrX == mFinalX && mCurrY == mFinalY) { 226 mFinished = true; 227 } 228 229 break; 230 } 231 } 232 else { 233 mCurrX = mFinalX; 234 mCurrY = mFinalY; 235 mFinished = true; 236 } 237 return true; 238 } 239 240 /** 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; 273 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); 288 } 289 290 /** 291 * Start scrolling based on a fling gesture. The distance travelled will 292 * depend on the initial velocity of the fling. 293 * 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 297 * second. 298 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 299 * second 300 * @param minX Minimum X value. The scroller will not scroll past this 301 * point. 302 * @param maxX Maximum X value. The scroller will not scroll past this 303 * point. 304 * @param minY Minimum Y value. The scroller will not scroll past this 305 * point. 306 * @param maxY Maximum Y value. The scroller will not scroll past this 307 * point. 308 */ 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; 312 mFinished = false; 313 314 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; 350 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 } 357 x *= mViscousFluidNormalize; 358 return x; 359 } 360 361 /** 362 * 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 365 * 366 * @see #forceFinished(boolean) 367 */ 368 public void abortAnimation() { 369 mCurrX = mFinalX; 370 mCurrY = mFinalY; 371 mFinished = true; 372 } 373 374 /** 375 * Extend the scroll animation. This allows a running animation to scroll 376 * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}. 377 * 378 * @param extend Additional time to scroll in milliseconds. 379 * @see #setFinalX(int) 380 * @see #setFinalY(int) 381 */ 382 public void extendDuration(int extend) { 383 int passed = timePassed(); 384 mDuration = passed + extend; 385 mDurationReciprocal = 1.0f / (float)mDuration; 386 mFinished = false; 387 } 388 389 /** 390 * Returns the time elapsed since the beginning of the scrolling. 391 * 392 * @return The elapsed time in milliseconds. 393 */ 394 public int timePassed() { 395 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 396 } 397 398 /** 399 * Sets the final position (X) for this scroller. 400 * 401 * @param newX The new X offset as an absolute distance from the origin. 402 * @see #extendDuration(int) 403 * @see #setFinalY(int) 404 */ 405 public void setFinalX(int newX) { 406 mFinalX = newX; 407 mDeltaX = mFinalX - mStartX; 408 mFinished = false; 409 } 410 411 /** 412 * Sets the final position (Y) for this scroller. 413 * 414 * @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) { 419 mFinalY = newY; 420 mDeltaY = mFinalY - mStartY; 421 mFinished = false; 422 } 423} 424