OverScroller.java revision 3fc3737ceb0f5c3b086472fb2cf7ebfb089e1bc8
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.animation.AccelerateDecelerateInterpolator; 21import android.view.animation.DecelerateInterpolator; 22 23/** 24 * This class encapsulates scrolling with the ability to overshoot the bounds 25 * of a scrolling operation. This class attempts to be a drop-in replacement 26 * for {@link android.widget.Scroller} in most cases. 27 * 28 * @hide Pending API approval 29 */ 30public class OverScroller { 31 private static final int SPRINGBACK_DURATION = 150; 32 private static final int OVERFLING_DURATION = 150; 33 34 private static final int MODE_DEFAULT = 0; 35 private static final int MODE_OVERFLING = 1; 36 private static final int MODE_SPRINGBACK = 2; 37 38 private Scroller mDefaultScroller; 39 private Scroller mDecelScroller; 40 private Scroller mAccelDecelScroller; 41 private Scroller mCurrScroller; 42 43 private int mScrollMode = MODE_DEFAULT; 44 45 private int mMinimumX; 46 private int mMinimumY; 47 private int mMaximumX; 48 private int mMaximumY; 49 50 public OverScroller(Context context) { 51 mDefaultScroller = new Scroller(context); 52 mDecelScroller = new Scroller(context, new DecelerateInterpolator()); 53 mAccelDecelScroller = new Scroller(context, new AccelerateDecelerateInterpolator()); 54 mCurrScroller = mDefaultScroller; 55 } 56 57 /** 58 * Call this when you want to know the new location. If it returns true, 59 * the animation is not yet finished. loc will be altered to provide the 60 * new location. 61 */ 62 public boolean computeScrollOffset() { 63 boolean inProgress = mCurrScroller.computeScrollOffset(); 64 65 switch (mScrollMode) { 66 case MODE_OVERFLING: 67 if (!inProgress) { 68 // Overfling ended 69 if (springback(mCurrScroller.getCurrX(), mCurrScroller.getCurrY(), 70 mMinimumX, mMaximumX, mMinimumY, mMaximumY, mAccelDecelScroller)) { 71 return mCurrScroller.computeScrollOffset(); 72 } else { 73 mCurrScroller = mDefaultScroller; 74 mScrollMode = MODE_DEFAULT; 75 } 76 } 77 break; 78 79 case MODE_SPRINGBACK: 80 if (!inProgress) { 81 mCurrScroller = mDefaultScroller; 82 mScrollMode = MODE_DEFAULT; 83 } 84 break; 85 86 case MODE_DEFAULT: 87 // Fling/autoscroll - did we go off the edge? 88 if (inProgress) { 89 Scroller scroller = mCurrScroller; 90 final int x = scroller.getCurrX(); 91 final int y = scroller.getCurrY(); 92 final int minX = mMinimumX; 93 final int maxX = mMaximumX; 94 final int minY = mMinimumY; 95 final int maxY = mMaximumY; 96 if (x < minX || x > maxX || y < minY || y > maxY) { 97 final int startx = scroller.getStartX(); 98 final int starty = scroller.getStartY(); 99 final int time = scroller.timePassed(); 100 final float timeSecs = time / 1000.f; 101 final float xvel = ((x - startx) / timeSecs); 102 final float yvel = ((y - starty) / timeSecs); 103 104 if ((x < minX && xvel > 0) || (y < minY && yvel > 0) || 105 (x > maxX && xvel < 0) || (y > maxY && yvel < 0)) { 106 // If our velocity would take us back into valid areas, 107 // try to springback rather than overfling. 108 if (springback(x, y, minX, maxX, minY, maxY)) { 109 return mCurrScroller.computeScrollOffset(); 110 } 111 } else { 112 overfling(x, y, xvel, yvel); 113 return mCurrScroller.computeScrollOffset(); 114 } 115 } 116 } 117 break; 118 } 119 120 return inProgress; 121 } 122 123 private void overfling(int startx, int starty, float xvel, float yvel) { 124 Scroller scroller = mDecelScroller; 125 final float durationSecs = (OVERFLING_DURATION / 1000.f); 126 int dx = (int)(xvel * durationSecs) / 8; 127 int dy = (int)(yvel * durationSecs) / 8; 128 scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION); 129 mCurrScroller.abortAnimation(); 130 mCurrScroller = scroller; 131 mScrollMode = MODE_OVERFLING; 132 } 133 134 /** 135 * Call this when you want to 'spring back' into a valid coordinate range. 136 * 137 * @param startX Starting X coordinate 138 * @param startY Starting Y coordinate 139 * @param minX Minimum valid X value 140 * @param maxX Maximum valid X value 141 * @param minY Minimum valid Y value 142 * @param maxY Minimum valid Y value 143 * @return true if a springback was initiated, false if startX/startY was 144 * already within the valid range. 145 */ 146 public boolean springback(int startX, int startY, int minX, int maxX, 147 int minY, int maxY) { 148 return springback(startX, startY, minX, maxX, minY, maxY, mDecelScroller); 149 } 150 151 private boolean springback(int startX, int startY, int minX, int maxX, 152 int minY, int maxY, Scroller scroller) { 153 int xoff = 0; 154 int yoff = 0; 155 if (startX < minX) { 156 xoff = minX - startX; 157 } else if (startX > maxX) { 158 xoff = maxX - startX; 159 } 160 if (startY < minY) { 161 yoff = minY - startY; 162 } else if (startY > maxY) { 163 yoff = maxY - startY; 164 } 165 166 if (xoff != 0 || yoff != 0) { 167 scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION); 168 mCurrScroller.abortAnimation(); 169 mCurrScroller = scroller; 170 mScrollMode = MODE_SPRINGBACK; 171 return true; 172 } 173 174 return false; 175 } 176 177 /** 178 * 179 * Returns whether the scroller has finished scrolling. 180 * 181 * @return True if the scroller has finished scrolling, false otherwise. 182 */ 183 public final boolean isFinished() { 184 return mCurrScroller.isFinished(); 185 } 186 187 /** 188 * Returns the current X offset in the scroll. 189 * 190 * @return The new X offset as an absolute distance from the origin. 191 */ 192 public final int getCurrX() { 193 return mCurrScroller.getCurrX(); 194 } 195 196 /** 197 * Returns the current Y offset in the scroll. 198 * 199 * @return The new Y offset as an absolute distance from the origin. 200 */ 201 public final int getCurrY() { 202 return mCurrScroller.getCurrY(); 203 } 204 205 /** 206 * Stops the animation, resets any springback/overfling and completes 207 * any standard flings/scrolls in progress. 208 */ 209 public void abortAnimation() { 210 mCurrScroller.abortAnimation(); 211 mCurrScroller = mDefaultScroller; 212 mScrollMode = MODE_DEFAULT; 213 mCurrScroller.abortAnimation(); 214 } 215 216 /** 217 * Start scrolling by providing a starting point and the distance to travel. 218 * The scroll will use the default value of 250 milliseconds for the 219 * duration. This version does not spring back to boundaries. 220 * 221 * @param startX Starting horizontal scroll offset in pixels. Positive 222 * numbers will scroll the content to the left. 223 * @param startY Starting vertical scroll offset in pixels. Positive numbers 224 * will scroll the content up. 225 * @param dx Horizontal distance to travel. Positive numbers will scroll the 226 * content to the left. 227 * @param dy Vertical distance to travel. Positive numbers will scroll the 228 * content up. 229 */ 230 public void startScroll(int startX, int startY, int dx, int dy) { 231 final int minX = Math.min(startX, startX + dx); 232 final int maxX = Math.max(startX, startX + dx); 233 final int minY = Math.min(startY, startY + dy); 234 final int maxY = Math.max(startY, startY + dy); 235 startScroll(startX, startY, dx, dy, minX, maxX, minY, maxY); 236 } 237 238 /** 239 * Start scrolling by providing a starting point and the distance to travel. 240 * The scroll will use the default value of 250 milliseconds for the 241 * duration. This version will spring back to the provided boundaries if 242 * the scroll value would take it too far. 243 * 244 * @param startX Starting horizontal scroll offset in pixels. Positive 245 * numbers will scroll the content to the left. 246 * @param startY Starting vertical scroll offset in pixels. Positive numbers 247 * will scroll the content up. 248 * @param dx Horizontal distance to travel. Positive numbers will scroll the 249 * content to the left. 250 * @param dy Vertical distance to travel. Positive numbers will scroll the 251 * content up. 252 * @param minX Minimum X value. The scroller will not scroll past this 253 * point. 254 * @param maxX Maximum X value. The scroller will not scroll past this 255 * point. 256 * @param minY Minimum Y value. The scroller will not scroll past this 257 * point. 258 * @param maxY Maximum Y value. The scroller will not scroll past this 259 * point. 260 */ 261 public void startScroll(int startX, int startY, int dx, int dy, 262 int minX, int maxX, int minY, int maxY) { 263 mCurrScroller.abortAnimation(); 264 mCurrScroller = mDefaultScroller; 265 mScrollMode = MODE_DEFAULT; 266 mMinimumX = minX; 267 mMaximumX = maxX; 268 mMinimumY = minY; 269 mMaximumY = maxY; 270 mCurrScroller.startScroll(startX, startY, dx, dy); 271 } 272 273 /** 274 * Start scrolling by providing a starting point and the distance to travel. 275 * 276 * @param startX Starting horizontal scroll offset in pixels. Positive 277 * numbers will scroll the content to the left. 278 * @param startY Starting vertical scroll offset in pixels. Positive numbers 279 * will scroll the content up. 280 * @param dx Horizontal distance to travel. Positive numbers will scroll the 281 * content to the left. 282 * @param dy Vertical distance to travel. Positive numbers will scroll the 283 * content up. 284 * @param duration Duration of the scroll in milliseconds. 285 */ 286 public void startScroll(int startX, int startY, int dx, int dy, int duration) { 287 mCurrScroller.abortAnimation(); 288 mCurrScroller = mDefaultScroller; 289 mScrollMode = MODE_DEFAULT; 290 mMinimumX = Math.min(startX, startX + dx); 291 mMinimumY = Math.min(startY, startY + dy); 292 mMaximumX = Math.max(startX, startX + dx); 293 mMaximumY = Math.max(startY, startY + dy); 294 mCurrScroller.startScroll(startX, startY, dx, dy, duration); 295 } 296 297 /** 298 * Returns the duration of the active scroll in progress; standard, fling, 299 * springback, or overfling. Does not account for any overflings or springback 300 * that may result. 301 */ 302 public int getDuration() { 303 return mCurrScroller.getDuration(); 304 } 305 306 /** 307 * Start scrolling based on a fling gesture. The distance travelled will 308 * depend on the initial velocity of the fling. 309 * 310 * @param startX Starting point of the scroll (X) 311 * @param startY Starting point of the scroll (Y) 312 * @param velocityX Initial velocity of the fling (X) measured in pixels per 313 * second. 314 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 315 * second 316 * @param minX Minimum X value. The scroller will not scroll past this 317 * point. 318 * @param maxX Maximum X value. The scroller will not scroll past this 319 * point. 320 * @param minY Minimum Y value. The scroller will not scroll past this 321 * point. 322 * @param maxY Maximum Y value. The scroller will not scroll past this 323 * point. 324 */ 325 public void fling(int startX, int startY, int velocityX, int velocityY, 326 int minX, int maxX, int minY, int maxY) { 327 this.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0); 328 } 329 330 /** 331 * Start scrolling based on a fling gesture. The distance travelled will 332 * depend on the initial velocity of the fling. 333 * 334 * @param startX Starting point of the scroll (X) 335 * @param startY Starting point of the scroll (Y) 336 * @param velocityX Initial velocity of the fling (X) measured in pixels per 337 * second. 338 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 339 * second 340 * @param minX Minimum X value. The scroller will not scroll past this 341 * point unless overX > 0. If overfling is allowed, it will use minX 342 * as a springback boundary. 343 * @param maxX Maximum X value. The scroller will not scroll past this 344 * point unless overX > 0. If overfling is allowed, it will use maxX 345 * as a springback boundary. 346 * @param minY Minimum Y value. The scroller will not scroll past this 347 * point unless overY > 0. If overfling is allowed, it will use minY 348 * as a springback boundary. 349 * @param maxY Maximum Y value. The scroller will not scroll past this 350 * point unless overY > 0. If overfling is allowed, it will use maxY 351 * as a springback boundary. 352 * @param overX Overfling range. If > 0, horizontal overfling in either 353 * direction will be possible. 354 * @param overY Overfling range. If > 0, vertical overfling in either 355 * direction will be possible. 356 */ 357 public void fling(int startX, int startY, int velocityX, int velocityY, 358 int minX, int maxX, int minY, int maxY, int overX, int overY) { 359 mCurrScroller = mDefaultScroller; 360 mScrollMode = MODE_DEFAULT; 361 mMinimumX = minX; 362 mMaximumX = maxX; 363 mMinimumY = minY; 364 mMaximumY = maxY; 365 mCurrScroller.fling(startX, startY, velocityX, velocityY, 366 minX - overX, maxX + overX, minY - overY, maxY + overY); 367 } 368 369 /** 370 * Returns where the scroll will end. Valid only for "fling" scrolls. 371 * 372 * @return The final X offset as an absolute distance from the origin. 373 */ 374 public int getFinalX() { 375 return mCurrScroller.getFinalX(); 376 } 377 378 /** 379 * Returns where the scroll will end. Valid only for "fling" scrolls. 380 * 381 * @return The final Y offset as an absolute distance from the origin. 382 */ 383 public int getFinalY() { 384 return mCurrScroller.getFinalY(); 385 } 386} 387