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 17 package android.support.v4.widget; 18 19 import android.content.Context; 20 import android.os.Build; 21 import android.view.animation.AnimationUtils; 22 import android.view.animation.Interpolator; 23 import 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 */ 32 public class ScrollerCompat { 33 private static final String TAG = "ScrollerCompat"; 34 35 Object mScroller; 36 ScrollerCompatImpl mImpl; 37 38 interface ScrollerCompatImpl { createScroller(Context context, Interpolator interpolator)39 Object createScroller(Context context, Interpolator interpolator); isFinished(Object scroller)40 boolean isFinished(Object scroller); getCurrX(Object scroller)41 int getCurrX(Object scroller); getCurrY(Object scroller)42 int getCurrY(Object scroller); getCurrVelocity(Object scroller)43 float getCurrVelocity(Object scroller); computeScrollOffset(Object scroller)44 boolean computeScrollOffset(Object scroller); startScroll(Object scroller, int startX, int startY, int dx, int dy)45 void startScroll(Object scroller, int startX, int startY, int dx, int dy); startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration)46 void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration); fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY)47 void fling(Object scroller, int startX, int startY, int velX, int velY, 48 int minX, int maxX, int minY, int maxY); fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY)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); abortAnimation(Object scroller)51 void abortAnimation(Object scroller); notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX)52 void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX); notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY)53 void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY); isOverScrolled(Object scroller)54 boolean isOverScrolled(Object scroller); getFinalX(Object scroller)55 int getFinalX(Object scroller); getFinalY(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 createScroller(Context context, Interpolator interpolator)63 public Object createScroller(Context context, Interpolator interpolator) { 64 return interpolator != null ? 65 new Scroller(context, interpolator) : new Scroller(context); 66 } 67 68 @Override isFinished(Object scroller)69 public boolean isFinished(Object scroller) { 70 return ((Scroller) scroller).isFinished(); 71 } 72 73 @Override getCurrX(Object scroller)74 public int getCurrX(Object scroller) { 75 return ((Scroller) scroller).getCurrX(); 76 } 77 78 @Override getCurrY(Object scroller)79 public int getCurrY(Object scroller) { 80 return ((Scroller) scroller).getCurrY(); 81 } 82 83 @Override getCurrVelocity(Object scroller)84 public float getCurrVelocity(Object scroller) { 85 return 0; 86 } 87 88 @Override computeScrollOffset(Object scroller)89 public boolean computeScrollOffset(Object scroller) { 90 final Scroller s = (Scroller) scroller; 91 return s.computeScrollOffset(); 92 } 93 94 @Override startScroll(Object scroller, int startX, int startY, int dx, int dy)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 startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration)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 fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY)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 fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY)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 abortAnimation(Object scroller)118 public void abortAnimation(Object scroller) { 119 ((Scroller) scroller).abortAnimation(); 120 } 121 122 @Override notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX)123 public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, 124 int overX) { 125 // No-op 126 } 127 128 @Override notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY)129 public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { 130 // No-op 131 } 132 133 @Override isOverScrolled(Object scroller)134 public boolean isOverScrolled(Object scroller) { 135 // Always false 136 return false; 137 } 138 139 @Override getFinalX(Object scroller)140 public int getFinalX(Object scroller) { 141 return ((Scroller) scroller).getFinalX(); 142 } 143 144 @Override getFinalY(Object scroller)145 public int getFinalY(Object scroller) { 146 return ((Scroller) scroller).getFinalY(); 147 } 148 } 149 150 static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl { 151 @Override createScroller(Context context, Interpolator interpolator)152 public Object createScroller(Context context, Interpolator interpolator) { 153 return ScrollerCompatGingerbread.createScroller(context, interpolator); 154 } 155 156 @Override isFinished(Object scroller)157 public boolean isFinished(Object scroller) { 158 return ScrollerCompatGingerbread.isFinished(scroller); 159 } 160 161 @Override getCurrX(Object scroller)162 public int getCurrX(Object scroller) { 163 return ScrollerCompatGingerbread.getCurrX(scroller); 164 } 165 166 @Override getCurrY(Object scroller)167 public int getCurrY(Object scroller) { 168 return ScrollerCompatGingerbread.getCurrY(scroller); 169 } 170 171 @Override getCurrVelocity(Object scroller)172 public float getCurrVelocity(Object scroller) { 173 return 0; 174 } 175 176 @Override computeScrollOffset(Object scroller)177 public boolean computeScrollOffset(Object scroller) { 178 return ScrollerCompatGingerbread.computeScrollOffset(scroller); 179 } 180 181 @Override startScroll(Object scroller, int startX, int startY, int dx, int dy)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 startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration)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 fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY)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 fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY)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 abortAnimation(Object scroller)207 public void abortAnimation(Object scroller) { 208 ScrollerCompatGingerbread.abortAnimation(scroller); 209 } 210 211 @Override notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX)212 public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, 213 int overX) { 214 ScrollerCompatGingerbread.notifyHorizontalEdgeReached(scroller, startX, finalX, overX); 215 } 216 217 @Override notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY)218 public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { 219 ScrollerCompatGingerbread.notifyVerticalEdgeReached(scroller, startY, finalY, overY); 220 } 221 222 @Override isOverScrolled(Object scroller)223 public boolean isOverScrolled(Object scroller) { 224 return ScrollerCompatGingerbread.isOverScrolled(scroller); 225 } 226 227 @Override getFinalX(Object scroller)228 public int getFinalX(Object scroller) { 229 return ScrollerCompatGingerbread.getFinalX(scroller); 230 } 231 232 @Override getFinalY(Object scroller)233 public int getFinalY(Object scroller) { 234 return ScrollerCompatGingerbread.getFinalY(scroller); 235 } 236 } 237 238 static class ScrollerCompatImplIcs extends ScrollerCompatImplGingerbread { 239 @Override getCurrVelocity(Object scroller)240 public float getCurrVelocity(Object scroller) { 241 return ScrollerCompatIcs.getCurrVelocity(scroller); 242 } 243 } 244 create(Context context)245 public static ScrollerCompat create(Context context) { 246 return create(context, null); 247 } 248 create(Context context, Interpolator interpolator)249 public static ScrollerCompat create(Context context, Interpolator interpolator) { 250 return new ScrollerCompat(context, interpolator); 251 } 252 ScrollerCompat(Context context, Interpolator interpolator)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 */ ScrollerCompat(int apiVersion, Context context, Interpolator interpolator)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 */ isFinished()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 */ getCurrX()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 */ getCurrY()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 */ getFinalX()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 */ getFinalY()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 */ getCurrVelocity()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 */ computeScrollOffset()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 */ startScroll(int startX, int startY, int dx, int dy)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 */ startScroll(int startX, int startY, int dx, int dy, int duration)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 */ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)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 */ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)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 */ abortAnimation()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 */ notifyHorizontalEdgeReached(int startX, int finalX, int overX)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 */ notifyVerticalEdgeReached(int startY, int finalY, int overY)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 */ isOverScrolled()481 public boolean isOverScrolled() { 482 return mImpl.isOverScrolled(mScroller); 483 } 484 } 485