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