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