1/* 2 * Copyright (C) 2012 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.support.v4.widget; 18 19import android.content.Context; 20import android.os.Build; 21import android.view.animation.AnimationUtils; 22import android.view.animation.Interpolator; 23import android.widget.Scroller; 24 25/** 26 * Provides access to new {@link android.widget.Scroller Scroller} APIs when available. 27 * 28 * <p>This class provides a platform version-independent mechanism for obeying the 29 * current device's preferred scroll physics and fling behavior. It offers a subset of 30 * the APIs from Scroller or OverScroller.</p> 31 */ 32public final class ScrollerCompat { 33 private static final String TAG = "ScrollerCompat"; 34 35 Object mScroller; 36 ScrollerCompatImpl mImpl; 37 38 interface ScrollerCompatImpl { 39 Object createScroller(Context context, Interpolator interpolator); 40 boolean isFinished(Object scroller); 41 int getCurrX(Object scroller); 42 int getCurrY(Object scroller); 43 float getCurrVelocity(Object scroller); 44 boolean computeScrollOffset(Object scroller); 45 void startScroll(Object scroller, int startX, int startY, int dx, int dy); 46 void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration); 47 void fling(Object scroller, int startX, int startY, int velX, int velY, 48 int minX, int maxX, int minY, int maxY); 49 void fling(Object scroller, int startX, int startY, int velX, int velY, 50 int minX, int maxX, int minY, int maxY, int overX, int overY); 51 void abortAnimation(Object scroller); 52 void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX); 53 void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY); 54 boolean isOverScrolled(Object scroller); 55 int getFinalX(Object scroller); 56 int getFinalY(Object scroller); 57 boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, 58 int minY, int maxY); 59 } 60 61 static final int CHASE_FRAME_TIME = 16; // ms per target frame 62 63 static class ScrollerCompatImplBase implements ScrollerCompatImpl { 64 @Override 65 public Object createScroller(Context context, Interpolator interpolator) { 66 return interpolator != null ? 67 new Scroller(context, interpolator) : new Scroller(context); 68 } 69 70 @Override 71 public boolean isFinished(Object scroller) { 72 return ((Scroller) scroller).isFinished(); 73 } 74 75 @Override 76 public int getCurrX(Object scroller) { 77 return ((Scroller) scroller).getCurrX(); 78 } 79 80 @Override 81 public int getCurrY(Object scroller) { 82 return ((Scroller) scroller).getCurrY(); 83 } 84 85 @Override 86 public float getCurrVelocity(Object scroller) { 87 return 0; 88 } 89 90 @Override 91 public boolean computeScrollOffset(Object scroller) { 92 final Scroller s = (Scroller) scroller; 93 return s.computeScrollOffset(); 94 } 95 96 @Override 97 public void startScroll(Object scroller, int startX, int startY, int dx, int dy) { 98 ((Scroller) scroller).startScroll(startX, startY, dx, dy); 99 } 100 101 @Override 102 public void startScroll(Object scroller, int startX, int startY, int dx, int dy, 103 int duration) { 104 ((Scroller) scroller).startScroll(startX, startY, dx, dy, duration); 105 } 106 107 @Override 108 public void fling(Object scroller, int startX, int startY, int velX, int velY, 109 int minX, int maxX, int minY, int maxY) { 110 ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY); 111 } 112 113 @Override 114 public void fling(Object scroller, int startX, int startY, int velX, int velY, 115 int minX, int maxX, int minY, int maxY, int overX, int overY) { 116 ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY); 117 } 118 119 @Override 120 public void abortAnimation(Object scroller) { 121 ((Scroller) scroller).abortAnimation(); 122 } 123 124 @Override 125 public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, 126 int overX) { 127 // No-op 128 } 129 130 @Override 131 public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { 132 // No-op 133 } 134 135 @Override 136 public boolean isOverScrolled(Object scroller) { 137 // Always false 138 return false; 139 } 140 141 @Override 142 public int getFinalX(Object scroller) { 143 return ((Scroller) scroller).getFinalX(); 144 } 145 146 @Override 147 public int getFinalY(Object scroller) { 148 return ((Scroller) scroller).getFinalY(); 149 } 150 151 @Override 152 public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, 153 int minY, int maxY) { 154 return false; 155 } 156 } 157 158 static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl { 159 @Override 160 public Object createScroller(Context context, Interpolator interpolator) { 161 return ScrollerCompatGingerbread.createScroller(context, interpolator); 162 } 163 164 @Override 165 public boolean isFinished(Object scroller) { 166 return ScrollerCompatGingerbread.isFinished(scroller); 167 } 168 169 @Override 170 public int getCurrX(Object scroller) { 171 return ScrollerCompatGingerbread.getCurrX(scroller); 172 } 173 174 @Override 175 public int getCurrY(Object scroller) { 176 return ScrollerCompatGingerbread.getCurrY(scroller); 177 } 178 179 @Override 180 public float getCurrVelocity(Object scroller) { 181 return 0; 182 } 183 184 @Override 185 public boolean computeScrollOffset(Object scroller) { 186 return ScrollerCompatGingerbread.computeScrollOffset(scroller); 187 } 188 189 @Override 190 public void startScroll(Object scroller, int startX, int startY, int dx, int dy) { 191 ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy); 192 } 193 194 @Override 195 public void startScroll(Object scroller, int startX, int startY, int dx, int dy, 196 int duration) { 197 ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy, duration); 198 } 199 200 @Override 201 public void fling(Object scroller, int startX, int startY, int velX, int velY, 202 int minX, int maxX, int minY, int maxY) { 203 ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY, 204 minX, maxX, minY, maxY); 205 } 206 207 @Override 208 public void fling(Object scroller, int startX, int startY, int velX, int velY, 209 int minX, int maxX, int minY, int maxY, int overX, int overY) { 210 ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY, 211 minX, maxX, minY, maxY, overX, overY); 212 } 213 214 @Override 215 public void abortAnimation(Object scroller) { 216 ScrollerCompatGingerbread.abortAnimation(scroller); 217 } 218 219 @Override 220 public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, 221 int overX) { 222 ScrollerCompatGingerbread.notifyHorizontalEdgeReached(scroller, startX, finalX, overX); 223 } 224 225 @Override 226 public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { 227 ScrollerCompatGingerbread.notifyVerticalEdgeReached(scroller, startY, finalY, overY); 228 } 229 230 @Override 231 public boolean isOverScrolled(Object scroller) { 232 return ScrollerCompatGingerbread.isOverScrolled(scroller); 233 } 234 235 @Override 236 public int getFinalX(Object scroller) { 237 return ScrollerCompatGingerbread.getFinalX(scroller); 238 } 239 240 @Override 241 public int getFinalY(Object scroller) { 242 return ScrollerCompatGingerbread.getFinalY(scroller); 243 } 244 245 @Override 246 public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, 247 int minY, int maxY) { 248 return ScrollerCompatGingerbread.springBack(scroller, startX, startY, minX, maxX, 249 minY, maxY); 250 } 251 } 252 253 static class ScrollerCompatImplIcs extends ScrollerCompatImplGingerbread { 254 @Override 255 public float getCurrVelocity(Object scroller) { 256 return ScrollerCompatIcs.getCurrVelocity(scroller); 257 } 258 } 259 260 public static ScrollerCompat create(Context context) { 261 return create(context, null); 262 } 263 264 public static ScrollerCompat create(Context context, Interpolator interpolator) { 265 return new ScrollerCompat(Build.VERSION.SDK_INT, context, interpolator); 266 } 267 268 /** 269 * Private constructer where API version can be provided. 270 * Useful for unit testing. 271 */ 272 private ScrollerCompat(int apiVersion, Context context, Interpolator interpolator) { 273 if (apiVersion >= 14) { // ICS 274 mImpl = new ScrollerCompatImplIcs(); 275 } else if (apiVersion>= 9) { // Gingerbread 276 mImpl = new ScrollerCompatImplGingerbread(); 277 } else { 278 mImpl = new ScrollerCompatImplBase(); 279 } 280 mScroller = mImpl.createScroller(context, interpolator); 281 } 282 283 /** 284 * Returns whether the scroller has finished scrolling. 285 * 286 * @return True if the scroller has finished scrolling, false otherwise. 287 */ 288 public boolean isFinished() { 289 return mImpl.isFinished(mScroller); 290 } 291 292 /** 293 * Returns the current X offset in the scroll. 294 * 295 * @return The new X offset as an absolute distance from the origin. 296 */ 297 public int getCurrX() { 298 return mImpl.getCurrX(mScroller); 299 } 300 301 /** 302 * Returns the current Y offset in the scroll. 303 * 304 * @return The new Y offset as an absolute distance from the origin. 305 */ 306 public int getCurrY() { 307 return mImpl.getCurrY(mScroller); 308 } 309 310 /** 311 * @return The final X position for the scroll in progress, if known. 312 */ 313 public int getFinalX() { 314 return mImpl.getFinalX(mScroller); 315 } 316 317 /** 318 * @return The final Y position for the scroll in progress, if known. 319 */ 320 public int getFinalY() { 321 return mImpl.getFinalY(mScroller); 322 } 323 324 /** 325 * Returns the current velocity on platform versions that support it. 326 * 327 * <p>The device must support at least API level 14 (Ice Cream Sandwich). 328 * On older platform versions this method will return 0. This method should 329 * only be used as input for nonessential visual effects such as {@link EdgeEffectCompat}.</p> 330 * 331 * @return The original velocity less the deceleration. Result may be 332 * negative. 333 */ 334 public float getCurrVelocity() { 335 return mImpl.getCurrVelocity(mScroller); 336 } 337 338 /** 339 * Call this when you want to know the new location. If it returns true, 340 * the animation is not yet finished. loc will be altered to provide the 341 * new location. 342 */ 343 public boolean computeScrollOffset() { 344 return mImpl.computeScrollOffset(mScroller); 345 } 346 347 /** 348 * Start scrolling by providing a starting point and the distance to travel. 349 * The scroll will use the default value of 250 milliseconds for the 350 * duration. 351 * 352 * @param startX Starting horizontal scroll offset in pixels. Positive 353 * numbers will scroll the content to the left. 354 * @param startY Starting vertical scroll offset in pixels. Positive numbers 355 * will scroll the content up. 356 * @param dx Horizontal distance to travel. Positive numbers will scroll the 357 * content to the left. 358 * @param dy Vertical distance to travel. Positive numbers will scroll the 359 * content up. 360 */ 361 public void startScroll(int startX, int startY, int dx, int dy) { 362 mImpl.startScroll(mScroller, startX, startY, dx, dy); 363 } 364 365 /** 366 * Start scrolling by providing a starting point and the distance to travel. 367 * 368 * @param startX Starting horizontal scroll offset in pixels. Positive 369 * numbers will scroll the content to the left. 370 * @param startY Starting vertical scroll offset in pixels. Positive numbers 371 * will scroll the content up. 372 * @param dx Horizontal distance to travel. Positive numbers will scroll the 373 * content to the left. 374 * @param dy Vertical distance to travel. Positive numbers will scroll the 375 * content up. 376 * @param duration Duration of the scroll in milliseconds. 377 */ 378 public void startScroll(int startX, int startY, int dx, int dy, int duration) { 379 mImpl.startScroll(mScroller, startX, startY, dx, dy, duration); 380 } 381 382 /** 383 * Start scrolling based on a fling gesture. The distance travelled will 384 * depend on the initial velocity of the fling. 385 * 386 * @param startX Starting point of the scroll (X) 387 * @param startY Starting point of the scroll (Y) 388 * @param velocityX Initial velocity of the fling (X) measured in pixels per 389 * second. 390 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 391 * second 392 * @param minX Minimum X value. The scroller will not scroll past this 393 * point. 394 * @param maxX Maximum X value. The scroller will not scroll past this 395 * point. 396 * @param minY Minimum Y value. The scroller will not scroll past this 397 * point. 398 * @param maxY Maximum Y value. The scroller will not scroll past this 399 * point. 400 */ 401 public void fling(int startX, int startY, int velocityX, int velocityY, 402 int minX, int maxX, int minY, int maxY) { 403 mImpl.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 404 } 405 406 /** 407 * Start scrolling based on a fling gesture. The distance travelled will 408 * depend on the initial velocity of the fling. 409 * 410 * @param startX Starting point of the scroll (X) 411 * @param startY Starting point of the scroll (Y) 412 * @param velocityX Initial velocity of the fling (X) measured in pixels per 413 * second. 414 * @param velocityY Initial velocity of the fling (Y) measured in pixels per 415 * second 416 * @param minX Minimum X value. The scroller will not scroll past this 417 * point. 418 * @param maxX Maximum X value. The scroller will not scroll past this 419 * point. 420 * @param minY Minimum Y value. The scroller will not scroll past this 421 * point. 422 * @param maxY Maximum Y value. The scroller will not scroll past this 423 * point. 424 * @param overX Overfling range. If > 0, horizontal overfling in either 425 * direction will be possible. 426 * @param overY Overfling range. If > 0, vertical overfling in either 427 * direction will be possible. 428 */ 429 public void fling(int startX, int startY, int velocityX, int velocityY, 430 int minX, int maxX, int minY, int maxY, int overX, int overY) { 431 mImpl.fling(mScroller, startX, startY, velocityX, velocityY, 432 minX, maxX, minY, maxY, overX, overY); 433 } 434 435 /** 436 * Call this when you want to 'spring back' into a valid coordinate range. 437 * 438 * @param startX Starting X coordinate 439 * @param startY Starting Y coordinate 440 * @param minX Minimum valid X value 441 * @param maxX Maximum valid X value 442 * @param minY Minimum valid Y value 443 * @param maxY Maximum valid Y value 444 * @return true if a springback was initiated, false if startX and startY were 445 * already within the valid range. 446 */ 447 public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) { 448 return mImpl.springBack(mScroller, startX, startY, minX, maxX, minY, maxY); 449 } 450 451 /** 452 * Stops the animation. Aborting the animation causes the scroller to move to the final x and y 453 * position. 454 */ 455 public void abortAnimation() { 456 mImpl.abortAnimation(mScroller); 457 } 458 459 460 /** 461 * Notify the scroller that we've reached a horizontal boundary. 462 * Normally the information to handle this will already be known 463 * when the animation is started, such as in a call to one of the 464 * fling functions. However there are cases where this cannot be known 465 * in advance. This function will transition the current motion and 466 * animate from startX to finalX as appropriate. 467 * 468 * @param startX Starting/current X position 469 * @param finalX Desired final X position 470 * @param overX Magnitude of overscroll allowed. This should be the maximum 471 * desired distance from finalX. Absolute value - must be positive. 472 */ 473 public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) { 474 mImpl.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX); 475 } 476 477 /** 478 * Notify the scroller that we've reached a vertical boundary. 479 * Normally the information to handle this will already be known 480 * when the animation is started, such as in a call to one of the 481 * fling functions. However there are cases where this cannot be known 482 * in advance. This function will animate a parabolic motion from 483 * startY to finalY. 484 * 485 * @param startY Starting/current Y position 486 * @param finalY Desired final Y position 487 * @param overY Magnitude of overscroll allowed. This should be the maximum 488 * desired distance from finalY. Absolute value - must be positive. 489 */ 490 public void notifyVerticalEdgeReached(int startY, int finalY, int overY) { 491 mImpl.notifyVerticalEdgeReached(mScroller, startY, finalY, overY); 492 } 493 494 /** 495 * Returns whether the current Scroller is currently returning to a valid position. 496 * Valid bounds were provided by the 497 * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method. 498 * 499 * One should check this value before calling 500 * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress 501 * to restore a valid position will then be stopped. The caller has to take into account 502 * the fact that the started scroll will start from an overscrolled position. 503 * 504 * @return true when the current position is overscrolled and in the process of 505 * interpolating back to a valid value. 506 */ 507 public boolean isOverScrolled() { 508 return mImpl.isOverScrolled(mScroller); 509 } 510} 511