Scroller.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 where the scroll will end. Valid only for "fling" scrolls. 136 * 137 * @return The final X offset as an absolute distance from the origin. 138 */ 139 public final int getFinalX() { 140 return mFinalX; 141 } 142 143 /** 144 * Returns where the scroll will end. Valid only for "fling" scrolls. 145 * 146 * @return The final Y offset as an absolute distance from the origin. 147 */ 148 public final int getFinalY() { 149 return mFinalY; 150 } 151 152 /** 153 * Call this when you want to know the new location. If it returns true, 154 * the animation is not yet finished. loc will be altered to provide the 155 * new location. 156 */ 157 public boolean computeScrollOffset() { 158 if (mFinished) { 159 return false; 160 } 161 162 int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 163 164 if (timePassed < mDuration) { 165 switch (mMode) { 166 case SCROLL_MODE: 167 float x = (float)timePassed * mDurationReciprocal; 168 169 if (mInterpolator == null) 170 x = viscousFluid(x); 171 else 172 x = mInterpolator.getInterpolation(x); 173 174 mCurrX = mStartX + Math.round(x * mDeltaX); 175 mCurrY = mStartY + Math.round(x * mDeltaY); 176 if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) { 177 mFinished = true; 178 } 179 break; 180 case FLING_MODE: 181 float timePassedSeconds = timePassed / 1000.0f; 182 float distance = (mVelocity * timePassedSeconds) 183 - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f); 184 185 mCurrX = mStartX + Math.round(distance * mCoeffX); 186 // Pin to mMinX <= mCurrX <= mMaxX 187 mCurrX = Math.min(mCurrX, mMaxX); 188 mCurrX = Math.max(mCurrX, mMinX); 189 190 mCurrY = mStartY + Math.round(distance * mCoeffY); 191 // Pin to mMinY <= mCurrY <= mMaxY 192 mCurrY = Math.min(mCurrY, mMaxY); 193 mCurrY = Math.max(mCurrY, mMinY); 194 195 if (mCurrX == mFinalX && mCurrY == mFinalY) { 196 mFinished = true; 197 } 198 199 break; 200 } 201 } 202 else { 203 mCurrX = mFinalX; 204 mCurrY = mFinalY; 205 mFinished = true; 206 } 207 return true; 208 } 209 210 /** 211 * Start scrolling by providing a starting point and the distance to travel. 212 * The scroll will use the default value of 250 milliseconds for the 213 * duration. 214 * 215 * @param startX Starting horizontal scroll offset in pixels. Positive 216 * numbers will scroll the content to the left. 217 * @param startY Starting vertical scroll offset in pixels. Positive numbers 218 * will scroll the content up. 219 * @param dx Horizontal distance to travel. Positive numbers will scroll the 220 * content to the left. 221 * @param dy Vertical distance to travel. Positive numbers will scroll the 222 * content up. 223 */ 224 public void startScroll(int startX, int startY, int dx, int dy) { 225 startScroll(startX, startY, dx, dy, DEFAULT_DURATION); 226 } 227 228 /** 229 * Start scrolling by providing a starting point and the distance to travel. 230 * 231 * @param startX Starting horizontal scroll offset in pixels. Positive 232 * numbers will scroll the content to the left. 233 * @param startY Starting vertical scroll offset in pixels. Positive numbers 234 * will scroll the content up. 235 * @param dx Horizontal distance to travel. Positive numbers will scroll the 236 * content to the left. 237 * @param dy Vertical distance to travel. Positive numbers will scroll the 238 * content up. 239 * @param duration Duration of the scroll in milliseconds. 240 */ 241 public void startScroll(int startX, int startY, int dx, int dy, int duration) { 242 mMode = SCROLL_MODE; 243 mFinished = false; 244 mDuration = duration; 245 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 246 mStartX = startX; 247 mStartY = startY; 248 mFinalX = startX + dx; 249 mFinalY = startY + dy; 250 mDeltaX = dx; 251 mDeltaY = dy; 252 mDurationReciprocal = 1.0f / (float) mDuration; 253 // This controls the viscous fluid effect (how much of it) 254 mViscousFluidScale = 8.0f; 255 // must be set to 1.0 (used in viscousFluid()) 256 mViscousFluidNormalize = 1.0f; 257 mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); 258 } 259 260 /** 261 * Start scrolling based on a fling gesture. The distance travelled will 262 * depend on the initial velocity of the fling. 263 * 264 * @param startX Starting point of the scroll (X) 265 * @param startY Starting point of the scroll (Y) 266 * @param velocityX Initial velocity of the fling (X) measured in pixels per 267 * second. 268 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 269 * second 270 * @param minX Minimum X value. The scroller will not scroll past this 271 * point. 272 * @param maxX Maximum X value. The scroller will not scroll past this 273 * point. 274 * @param minY Minimum Y value. The scroller will not scroll past this 275 * point. 276 * @param maxY Maximum Y value. The scroller will not scroll past this 277 * point. 278 */ 279 public void fling(int startX, int startY, int velocityX, int velocityY, 280 int minX, int maxX, int minY, int maxY) { 281 mMode = FLING_MODE; 282 mFinished = false; 283 284 float velocity = (float)Math.hypot(velocityX, velocityY); 285 286 mVelocity = velocity; 287 mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in 288 // milliseconds 289 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 290 mStartX = startX; 291 mStartY = startY; 292 293 mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 294 mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity; 295 296 int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration)); 297 298 mMinX = minX; 299 mMaxX = maxX; 300 mMinY = minY; 301 mMaxY = maxY; 302 303 304 mFinalX = startX + Math.round(totalDistance * mCoeffX); 305 // Pin to mMinX <= mFinalX <= mMaxX 306 mFinalX = Math.min(mFinalX, mMaxX); 307 mFinalX = Math.max(mFinalX, mMinX); 308 309 mFinalY = startY + Math.round(totalDistance * mCoeffY); 310 // Pin to mMinY <= mFinalY <= mMaxY 311 mFinalY = Math.min(mFinalY, mMaxY); 312 mFinalY = Math.max(mFinalY, mMinY); 313 } 314 315 316 317 private float viscousFluid(float x) 318 { 319 x *= mViscousFluidScale; 320 if (x < 1.0f) { 321 x -= (1.0f - (float)Math.exp(-x)); 322 } else { 323 float start = 0.36787944117f; // 1/e == exp(-1) 324 x = 1.0f - (float)Math.exp(1.0f - x); 325 x = start + x * (1.0f - start); 326 } 327 x *= mViscousFluidNormalize; 328 return x; 329 } 330 331 /** 332 * 333 */ 334 public void abortAnimation() { 335 mCurrX = mFinalX; 336 mCurrY = mFinalY; 337 mFinished = true; 338 } 339 340 /** 341 * Extend the scroll animation. This allows a running animation to 342 * scroll further and longer, when used with setFinalX() or setFinalY(). 343 * 344 * @param extend Additional time to scroll in milliseconds. 345 */ 346 public void extendDuration(int extend) { 347 int passed = timePassed(); 348 mDuration = passed + extend; 349 mDurationReciprocal = 1.0f / (float)mDuration; 350 mFinished = false; 351 } 352 353 public int timePassed() { 354 return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 355 } 356 357 public void setFinalX(int newX) { 358 mFinalX = newX; 359 mDeltaX = mFinalX - mStartX; 360 mFinished = false; 361 } 362 363 public void setFinalY(int newY) { 364 mFinalY = newY; 365 mDeltaY = mFinalY - mStartY; 366 mFinished = false; 367 } 368} 369