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