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