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 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 */ Scroller(Context context)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 */ Scroller(Context context, Interpolator interpolator)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 */ isFinished()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 */ forceFinished(boolean finished)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 */ getDuration()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 */ getCurrX()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 */ getCurrY()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 */ getCurrVelocity()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 */ getStartX()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 */ getStartY()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 */ getFinalX()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 */ getFinalY()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 */ computeScrollOffset()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 */ startScroll(int startX, int startY, int dx, int dy)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 */ startScroll(int startX, int startY, int dx, int dy, int duration)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 */ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)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 viscousFluid(float x)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 */ abortAnimation()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 */ extendDuration(int extend)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 */ timePassed()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 */ setFinalX(int newX)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 */ setFinalY(int newY)418 public void setFinalY(int newY) { 419 mFinalY = newY; 420 mDeltaY = mFinalY - mStartY; 421 mFinished = false; 422 } 423 } 424