AutoScrollHelper.java revision 0d3da1232bf967e427477ab4d4c58eb3e933f17e
1/* 2 * Copyright (C) 2013 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 com.android.internal.widget; 18 19import android.content.res.Resources; 20import android.os.SystemClock; 21import android.util.DisplayMetrics; 22import android.view.MotionEvent; 23import android.view.View; 24import android.view.ViewConfiguration; 25import android.view.animation.AccelerateInterpolator; 26import android.view.animation.AnimationUtils; 27import android.view.animation.Interpolator; 28import android.widget.AbsListView; 29 30/** 31 * AutoScrollHelper is a utility class for adding automatic edge-triggered 32 * scrolling to Views. 33 * <p> 34 * <b>Note:</b> Implementing classes are responsible for overriding the 35 * {@link #onScrollBy} method to scroll the target view. See 36 * {@link AbsListViewAutoScroller} for an {@link android.widget.AbsListView} 37 * -specific implementation. 38 * <p> 39 * <h1>Activation</h1> Automatic scrolling starts when the user touches within 40 * an activation area. By default, activation areas are defined as the top, 41 * left, right, and bottom 20% of the host view's total area. Touching within 42 * the top activation area scrolls up, left scrolls to the left, and so on. 43 * <p> 44 * As the user touches closer to the extreme edge of the activation area, 45 * scrolling accelerates up to a maximum velocity. When using the default edge 46 * type, {@link #EDGE_TYPE_INSIDE_EXTEND}, moving outside of the view bounds 47 * will scroll at the maximum velocity. 48 * <p> 49 * The following activation properties may be configured: 50 * <ul> 51 * <li>Delay after entering activation area before auto-scrolling begins, see 52 * {@link #setActivationDelay}. Default value is 53 * {@link ViewConfiguration#getTapTimeout()} to avoid conflicting with taps. 54 * <li>Location of activation areas, see {@link #setEdgeType}. Default value is 55 * {@link #EDGE_TYPE_INSIDE_EXTEND}. 56 * <li>Size of activation areas relative to view size, see 57 * {@link #setRelativeEdges}. Default value is 20% for both vertical and 58 * horizontal edges. 59 * <li>Maximum size used to constrain relative size, see 60 * {@link #setMaximumEdges}. Default value is {@link #NO_MAX}. 61 * </ul> 62 * <h1>Scrolling</h1> When automatic scrolling is active, the helper will 63 * repeatedly call {@link #onScrollBy} to apply new scrolling offsets. 64 * <p> 65 * The following scrolling properties may be configured: 66 * <ul> 67 * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default 68 * value is 2.5 seconds. 69 * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}. 70 * Default value is 100% per second for both vertical and horizontal. 71 * <li>Minimum velocity used to constrain relative velocity, see 72 * {@link #setMinimumVelocity}. When set, scrolling will accelerate to the 73 * larger of either this value or the relative target value. Default value is 74 * approximately 5 centimeters or 315 dips per second. 75 * <li>Maximum velocity used to constrain relative velocity, see 76 * {@link #setMaximumVelocity}. Default value is approximately 25 centimeters or 77 * 1575 dips per second. 78 * </ul> 79 */ 80public abstract class AutoScrollHelper implements View.OnTouchListener { 81 /** 82 * Constant passed to {@link #setRelativeEdges} or 83 * {@link #setRelativeVelocity}. Using this value ensures that the computed 84 * relative value is ignored and the absolute maximum value is always used. 85 */ 86 public static final float RELATIVE_UNSPECIFIED = 0; 87 88 /** 89 * Constant passed to {@link #setMaximumEdges}, {@link #setMaximumVelocity}, 90 * or {@link #setMinimumVelocity}. Using this value ensures that the 91 * computed relative value is always used without constraining to a 92 * particular minimum or maximum value. 93 */ 94 public static final float NO_MAX = Float.MAX_VALUE; 95 96 /** 97 * Constant passed to {@link #setMaximumEdges}, or 98 * {@link #setMaximumVelocity}, or {@link #setMinimumVelocity}. Using this 99 * value ensures that the computed relative value is always used without 100 * constraining to a particular minimum or maximum value. 101 */ 102 public static final float NO_MIN = 0; 103 104 /** 105 * Edge type that specifies an activation area starting at the view bounds 106 * and extending inward. Moving outside the view bounds will stop scrolling. 107 * 108 * @see #setEdgeType 109 */ 110 public static final int EDGE_TYPE_INSIDE = 0; 111 112 /** 113 * Edge type that specifies an activation area starting at the view bounds 114 * and extending inward. After activation begins, moving outside the view 115 * bounds will continue scrolling. 116 * 117 * @see #setEdgeType 118 */ 119 public static final int EDGE_TYPE_INSIDE_EXTEND = 1; 120 121 /** 122 * Edge type that specifies an activation area starting at the view bounds 123 * and extending outward. Moving inside the view bounds will stop scrolling. 124 * 125 * @see #setEdgeType 126 */ 127 public static final int EDGE_TYPE_OUTSIDE = 2; 128 129 private static final int HORIZONTAL = 0; 130 private static final int VERTICAL = 1; 131 132 /** Scroller used to control acceleration toward maximum velocity. */ 133 private final ClampedScroller mScroller = new ClampedScroller(); 134 135 /** Interpolator used to scale velocity with touch position. */ 136 private final Interpolator mEdgeInterpolator = new AccelerateInterpolator(); 137 138 /** The view to auto-scroll. Might not be the source of touch events. */ 139 private final View mTarget; 140 141 /** Runnable used to animate scrolling. */ 142 private Runnable mRunnable; 143 144 /** Edge insets used to activate auto-scrolling. */ 145 private float[] mRelativeEdges = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; 146 147 /** Clamping values for edge insets used to activate auto-scrolling. */ 148 private float[] mMaximumEdges = new float[] { NO_MAX, NO_MAX }; 149 150 /** The type of edge being used. */ 151 private int mEdgeType; 152 153 /** Delay after entering an activation edge before auto-scrolling begins. */ 154 private int mActivationDelay; 155 156 /** Relative scrolling velocity at maximum edge distance. */ 157 private float[] mRelativeVelocity = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; 158 159 /** Clamping values used for scrolling velocity. */ 160 private float[] mMinimumVelocity = new float[] { NO_MIN, NO_MIN }; 161 162 /** Clamping values used for scrolling velocity. */ 163 private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX }; 164 165 /** Whether to start activation immediately. */ 166 private boolean mSkipDelay; 167 168 /** Whether to reset the scroller start time on the next animation. */ 169 private boolean mResetScroller; 170 171 /** Whether the auto-scroller is active. */ 172 private boolean mActive; 173 174 /** Whether the auto-scroller is scrolling. */ 175 private boolean mScrolling; 176 177 /** Whether the auto-scroller is enabled. */ 178 private boolean mEnabled; 179 180 /** Whether the auto-scroller consumes events when scrolling. */ 181 private boolean mExclusiveEnabled; 182 183 /** Down time of the most recent down touch event. */ 184 private long mDownTime; 185 186 // Default values. 187 private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND; 188 private static final int DEFAULT_MINIMUM_VELOCITY_DIPS = 315; 189 private static final int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575; 190 private static final float DEFAULT_MAXIMUM_EDGE = NO_MAX; 191 private static final float DEFAULT_RELATIVE_EDGE = 0.2f; 192 private static final float DEFAULT_RELATIVE_VELOCITY = 1f; 193 private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout(); 194 private static final int DEFAULT_RAMP_UP_DURATION = 2500; 195 // TODO: RAMP_DOWN_DURATION of 500ms? 196 197 /** 198 * Creates a new helper for scrolling the specified target view. 199 * <p> 200 * The resulting helper may be configured by chaining setter calls and 201 * should be set as a touch listener on the target view. 202 * <p> 203 * By default, the helper is disabled and will not respond to touch events 204 * until it is enabled using {@link #setEnabled}. 205 * 206 * @param target The view to automatically scroll. 207 */ 208 public AutoScrollHelper(View target) { 209 mTarget = target; 210 211 final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); 212 final int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f); 213 final int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f); 214 setMaximumVelocity(maxVelocity, maxVelocity); 215 setMinimumVelocity(minVelocity, minVelocity); 216 217 setEdgeType(DEFAULT_EDGE_TYPE); 218 setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE); 219 setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE); 220 setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY); 221 setActivationDelay(DEFAULT_ACTIVATION_DELAY); 222 setRampUpDuration(DEFAULT_RAMP_UP_DURATION); 223 224 mEnabled = true; 225 } 226 227 /** 228 * Sets whether the scroll helper is enabled and should respond to touch 229 * events. 230 * 231 * @param enabled Whether the scroll helper is enabled. 232 * @return The scroll helper, which may used to chain setter calls. 233 */ 234 public AutoScrollHelper setEnabled(boolean enabled) { 235 if (!enabled) { 236 stop(true); 237 } 238 239 mEnabled = enabled; 240 return this; 241 } 242 243 /** 244 * @return True if this helper is enabled and responding to touch events. 245 */ 246 public boolean isEnabled() { 247 return mEnabled; 248 } 249 250 /** 251 * Enables or disables exclusive handling of touch events during scrolling. 252 * By default, exclusive handling is disabled and the target view receives 253 * all touch events. 254 * <p> 255 * When enabled, {@link #onTouch} will return true if the helper is 256 * currently scrolling and false otherwise. 257 * 258 * @param enabled True to exclusively handle touch events during scrolling, 259 * false to allow the target view to receive all touch events. 260 * @see #isExclusiveEnabled() 261 * @see #onTouch(View, MotionEvent) 262 */ 263 public void setExclusiveEnabled(boolean enabled) { 264 mExclusiveEnabled = enabled; 265 } 266 267 /** 268 * Indicates whether the scroll helper handles touch events exclusively 269 * during scrolling. 270 * 271 * @return True if exclusive handling of touch events during scrolling is 272 * enabled, false otherwise. 273 * @see #setExclusiveEnabled(boolean) 274 */ 275 public boolean isExclusiveEnabled() { 276 return mExclusiveEnabled; 277 } 278 279 /** 280 * Sets the absolute maximum scrolling velocity. 281 * <p> 282 * If relative velocity is not specified, scrolling will always reach the 283 * same maximum velocity. If both relative and maximum velocities are 284 * specified, the maximum velocity will be used to clamp the calculated 285 * relative velocity. 286 * 287 * @param horizontalMax The maximum horizontal scrolling velocity, or 288 * {@link #NO_MAX} to leave the relative value unconstrained. 289 * @param verticalMax The maximum vertical scrolling velocity, or 290 * {@link #NO_MAX} to leave the relative value unconstrained. 291 * @return The scroll helper, which may used to chain setter calls. 292 */ 293 public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) { 294 mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f; 295 mMaximumVelocity[VERTICAL] = verticalMax / 1000f; 296 return this; 297 } 298 299 /** 300 * Sets the absolute minimum scrolling velocity. 301 * <p> 302 * If both relative and minimum velocities are specified, the minimum 303 * velocity will be used to clamp the calculated relative velocity. 304 * 305 * @param horizontalMin The minimum horizontal scrolling velocity, or 306 * {@link #NO_MIN} to leave the relative value unconstrained. 307 * @param verticalMin The minimum vertical scrolling velocity, or 308 * {@link #NO_MIN} to leave the relative value unconstrained. 309 * @return The scroll helper, which may used to chain setter calls. 310 */ 311 public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) { 312 mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f; 313 mMinimumVelocity[VERTICAL] = verticalMin / 1000f; 314 return this; 315 } 316 317 /** 318 * Sets the target scrolling velocity relative to the host view's 319 * dimensions. 320 * <p> 321 * If both relative and maximum velocities are specified, the maximum 322 * velocity will be used to clamp the calculated relative velocity. 323 * 324 * @param horizontal The target horizontal velocity as a fraction of the 325 * host view width per second, or {@link #RELATIVE_UNSPECIFIED} 326 * to ignore. 327 * @param vertical The target vertical velocity as a fraction of the host 328 * view height per second, or {@link #RELATIVE_UNSPECIFIED} to 329 * ignore. 330 * @return The scroll helper, which may used to chain setter calls. 331 */ 332 public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) { 333 mRelativeVelocity[HORIZONTAL] = horizontal / 1000f; 334 mRelativeVelocity[VERTICAL] = vertical / 1000f; 335 return this; 336 } 337 338 /** 339 * Sets the activation edge type, one of: 340 * <ul> 341 * <li>{@link #EDGE_TYPE_INSIDE} for edges that respond to touches inside 342 * the bounds of the host view. If touch moves outside the bounds, scrolling 343 * will stop. 344 * <li>{@link #EDGE_TYPE_INSIDE_EXTEND} for inside edges that continued to 345 * scroll when touch moves outside the bounds of the host view. 346 * <li>{@link #EDGE_TYPE_OUTSIDE} for edges that only respond to touches 347 * that move outside the bounds of the host view. 348 * </ul> 349 * 350 * @param type The type of edge to use. 351 * @return The scroll helper, which may used to chain setter calls. 352 */ 353 public AutoScrollHelper setEdgeType(int type) { 354 mEdgeType = type; 355 return this; 356 } 357 358 /** 359 * Sets the activation edge size relative to the host view's dimensions. 360 * <p> 361 * If both relative and maximum edges are specified, the maximum edge will 362 * be used to constrain the calculated relative edge size. 363 * 364 * @param horizontal The horizontal edge size as a fraction of the host view 365 * width, or {@link #RELATIVE_UNSPECIFIED} to always use the 366 * maximum value. 367 * @param vertical The vertical edge size as a fraction of the host view 368 * height, or {@link #RELATIVE_UNSPECIFIED} to always use the 369 * maximum value. 370 * @return The scroll helper, which may used to chain setter calls. 371 */ 372 public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) { 373 mRelativeEdges[HORIZONTAL] = horizontal; 374 mRelativeEdges[VERTICAL] = vertical; 375 return this; 376 } 377 378 /** 379 * Sets the absolute maximum edge size. 380 * <p> 381 * If relative edge size is not specified, activation edges will always be 382 * the maximum edge size. If both relative and maximum edges are specified, 383 * the maximum edge will be used to constrain the calculated relative edge 384 * size. 385 * 386 * @param horizontalMax The maximum horizontal edge size in pixels, or 387 * {@link #NO_MAX} to use the unconstrained calculated relative 388 * value. 389 * @param verticalMax The maximum vertical edge size in pixels, or 390 * {@link #NO_MAX} to use the unconstrained calculated relative 391 * value. 392 * @return The scroll helper, which may used to chain setter calls. 393 */ 394 public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) { 395 mMaximumEdges[HORIZONTAL] = horizontalMax; 396 mMaximumEdges[VERTICAL] = verticalMax; 397 return this; 398 } 399 400 /** 401 * Sets the delay after entering an activation edge before activation of 402 * auto-scrolling. By default, the activation delay is set to 403 * {@link ViewConfiguration#getTapTimeout()}. 404 * <p> 405 * Specifying a delay of zero will start auto-scrolling immediately after 406 * the touch position enters an activation edge. 407 * 408 * @param delayMillis The activation delay in milliseconds. 409 * @return The scroll helper, which may used to chain setter calls. 410 */ 411 public AutoScrollHelper setActivationDelay(int delayMillis) { 412 mActivationDelay = delayMillis; 413 return this; 414 } 415 416 /** 417 * Sets the amount of time after activation of auto-scrolling that is takes 418 * to reach target velocity for the current touch position. 419 * <p> 420 * Specifying a duration greater than zero prevents sudden jumps in 421 * velocity. 422 * 423 * @param durationMillis The ramp-up duration in milliseconds. 424 * @return The scroll helper, which may used to chain setter calls. 425 */ 426 public AutoScrollHelper setRampUpDuration(int durationMillis) { 427 mScroller.setDuration(durationMillis); 428 return this; 429 } 430 431 /** 432 * Handles touch events by activating automatic scrolling, adjusting scroll 433 * velocity, or stopping. 434 * <p> 435 * If {@link #isExclusiveEnabled()} is false, always returns false so that 436 * the host view may handle touch events. Otherwise, returns true when 437 * automatic scrolling is active and false otherwise. 438 */ 439 @Override 440 public boolean onTouch(View v, MotionEvent event) { 441 if (!mEnabled) { 442 return false; 443 } 444 445 final int action = event.getActionMasked(); 446 switch (action) { 447 case MotionEvent.ACTION_DOWN: 448 mDownTime = event.getDownTime(); 449 case MotionEvent.ACTION_MOVE: 450 final float xValue = getEdgeValue(mRelativeEdges[HORIZONTAL], v.getWidth(), 451 mMaximumEdges[HORIZONTAL], event.getX()); 452 final float yValue = getEdgeValue(mRelativeEdges[VERTICAL], v.getHeight(), 453 mMaximumEdges[VERTICAL], event.getY()); 454 final float maxVelX = constrain(mRelativeVelocity[HORIZONTAL] * mTarget.getWidth(), 455 mMinimumVelocity[HORIZONTAL], mMaximumVelocity[HORIZONTAL]); 456 final float maxVelY = constrain(mRelativeVelocity[VERTICAL] * mTarget.getHeight(), 457 mMinimumVelocity[VERTICAL], mMaximumVelocity[VERTICAL]); 458 mScroller.setTargetVelocity(xValue * maxVelX, yValue * maxVelY); 459 460 if ((xValue != 0 || yValue != 0) && !mActive) { 461 mActive = true; 462 mResetScroller = true; 463 if (mRunnable == null) { 464 mRunnable = new AutoScrollRunnable(); 465 } 466 if (mSkipDelay) { 467 mTarget.postOnAnimation(mRunnable); 468 } else { 469 mSkipDelay = true; 470 mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay); 471 } 472 } 473 break; 474 case MotionEvent.ACTION_UP: 475 case MotionEvent.ACTION_CANCEL: 476 stop(true); 477 break; 478 } 479 480 return mExclusiveEnabled && mScrolling; 481 } 482 483 /** 484 * Override this method to scroll the target view by the specified number 485 * of pixels. 486 * <p> 487 * Returns whether the target view was able to scroll the requested amount. 488 * 489 * @param deltaX The amount to scroll in the X direction, in pixels. 490 * @param deltaY The amount to scroll in the Y direction, in pixels. 491 * @return true if the target view was able to scroll the requested amount. 492 */ 493 public abstract boolean onScrollBy(int deltaX, int deltaY); 494 495 /** 496 * Returns the interpolated position of a touch point relative to an edge 497 * defined by its relative inset, its maximum absolute inset, and the edge 498 * interpolator. 499 * 500 * @param relativeValue The size of the inset relative to the total size. 501 * @param size Total size. 502 * @param maxValue The maximum size of the inset, used to clamp (relative * 503 * total). 504 * @param current Touch position within within the total size. 505 * @return Interpolated value of the touch position within the edge. 506 */ 507 private float getEdgeValue(float relativeValue, float size, float maxValue, float current) { 508 // For now, leading and trailing edges are always the same size. 509 final float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue); 510 final float valueLeading = constrainEdgeValue(current, edgeSize); 511 final float valueTrailing = constrainEdgeValue(size - current, edgeSize); 512 final float value = (valueTrailing - valueLeading); 513 final float interpolated; 514 if (value < 0) { 515 interpolated = -mEdgeInterpolator.getInterpolation(-value); 516 } else if (value > 0) { 517 interpolated = mEdgeInterpolator.getInterpolation(value); 518 } else { 519 return 0; 520 } 521 522 return constrain(interpolated, -1, 1); 523 } 524 525 private float constrainEdgeValue(float current, float leading) { 526 if (leading == 0) { 527 return 0; 528 } 529 530 switch (mEdgeType) { 531 case EDGE_TYPE_INSIDE: 532 case EDGE_TYPE_INSIDE_EXTEND: 533 if (current < leading) { 534 if (current >= 0) { 535 // Movement up to the edge is scaled. 536 return 1f - current / leading; 537 } else if (mActive && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) { 538 // Movement beyond the edge is always maximum. 539 return 1f; 540 } 541 } 542 break; 543 case EDGE_TYPE_OUTSIDE: 544 if (current < 0) { 545 // Movement beyond the edge is scaled. 546 return current / -leading; 547 } 548 break; 549 } 550 551 return 0; 552 } 553 554 private static float constrain(float value, float min, float max) { 555 if (value > max) { 556 return max; 557 } else if (value < min) { 558 return min; 559 } else { 560 return value; 561 } 562 } 563 564 /** 565 * Stops auto-scrolling immediately, optionally reseting the auto-scrolling 566 * delay. 567 * 568 * @param reset Whether to reset the auto-scrolling delay. 569 */ 570 private void stop(boolean reset) { 571 mActive = false; 572 mScrolling = false; 573 mSkipDelay = !reset; 574 575 if (mRunnable != null) { 576 mTarget.removeCallbacks(mRunnable); 577 } 578 } 579 580 /** 581 * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view, 582 * canceling any ongoing touch events. 583 */ 584 private void cancelTargetTouch() { 585 final MotionEvent cancel = MotionEvent.obtain( 586 mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0); 587 cancel.setAction(MotionEvent.ACTION_CANCEL); 588 mTarget.onTouchEvent(cancel); 589 cancel.recycle(); 590 } 591 592 private class AutoScrollRunnable implements Runnable { 593 @Override 594 public void run() { 595 if (!mActive) { 596 return; 597 } 598 599 if (mResetScroller) { 600 mResetScroller = false; 601 mScroller.start(); 602 } 603 604 final View target = mTarget; 605 final ClampedScroller scroller = mScroller; 606 scroller.computeScrollDelta(); 607 608 final int deltaX = scroller.getDeltaX(); 609 final int deltaY = scroller.getDeltaY(); 610 if ((deltaX != 0 || deltaY != 0 || !scroller.isFinished()) 611 && onScrollBy(deltaX, deltaY)) { 612 // Update whether we're actively scrolling. 613 final boolean scrolling = (deltaX != 0 || deltaY != 0); 614 if (mScrolling != scrolling) { 615 mScrolling = scrolling; 616 617 // If we just started actively scrolling, make sure any down 618 // or move events send to the target view are canceled. 619 if (mExclusiveEnabled && scrolling) { 620 cancelTargetTouch(); 621 } 622 } 623 624 // Keep going until the scroller has permanently stopped or the 625 // view can't scroll any more. If the user moves their finger 626 // again, we'll repost the animation. 627 target.postOnAnimation(this); 628 } else { 629 stop(false); 630 } 631 } 632 } 633 634 /** 635 * Scroller whose velocity follows the curve of an {@link Interpolator} and 636 * is clamped to the interpolated 0f value before starting and the 637 * interpolated 1f value after a specified duration. 638 */ 639 private static class ClampedScroller { 640 private final Interpolator mInterpolator = new AccelerateInterpolator(); 641 642 private int mDuration; 643 private float mTargetVelocityX; 644 private float mTargetVelocityY; 645 646 private long mStartTime; 647 private long mDeltaTime; 648 private int mDeltaX; 649 private int mDeltaY; 650 651 /** 652 * Creates a new ramp-up scroller that reaches full velocity after a 653 * specified duration. 654 */ 655 public ClampedScroller() { 656 reset(); 657 } 658 659 public void setDuration(int durationMillis) { 660 mDuration = durationMillis; 661 } 662 663 /** 664 * Starts the scroller at the current animation time. 665 */ 666 public void start() { 667 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 668 mDeltaTime = mStartTime; 669 } 670 671 /** 672 * Returns whether the scroller is finished, which means that its 673 * acceleration is zero. 674 * 675 * @return Whether the scroller is finished. 676 */ 677 public boolean isFinished() { 678 if (mTargetVelocityX == 0 && mTargetVelocityY == 0) { 679 return true; 680 } 681 final long currentTime = AnimationUtils.currentAnimationTimeMillis(); 682 final long elapsedSinceStart = currentTime - mStartTime; 683 return elapsedSinceStart > mDuration; 684 } 685 686 /** 687 * Stops the scroller and resets its values. 688 */ 689 public void reset() { 690 mStartTime = -1; 691 mDeltaTime = -1; 692 mDeltaX = 0; 693 mDeltaY = 0; 694 } 695 696 /** 697 * Computes the current scroll deltas. This usually only be called after 698 * starting the scroller with {@link #start()}. 699 * 700 * @see #getDeltaX() 701 * @see #getDeltaY() 702 */ 703 public void computeScrollDelta() { 704 final long currentTime = AnimationUtils.currentAnimationTimeMillis(); 705 final long elapsedSinceStart = currentTime - mStartTime; 706 final float value; 707 if (mStartTime < 0) { 708 value = 0f; 709 } else if (elapsedSinceStart < mDuration) { 710 value = (float) elapsedSinceStart / mDuration; 711 } else { 712 value = 1f; 713 } 714 715 final float scale = mInterpolator.getInterpolation(value); 716 final long elapsedSinceDelta = currentTime - mDeltaTime; 717 718 mDeltaTime = currentTime; 719 mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX); 720 mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY); 721 } 722 723 /** 724 * Sets the target velocity for this scroller. 725 * 726 * @param x The target X velocity in pixels per millisecond. 727 * @param y The target Y velocity in pixels per millisecond. 728 */ 729 public void setTargetVelocity(float x, float y) { 730 mTargetVelocityX = x; 731 mTargetVelocityY = y; 732 } 733 734 /** 735 * The distance traveled in the X-coordinate computed by the last call 736 * to {@link #computeScrollDelta()}. 737 */ 738 public int getDeltaX() { 739 return mDeltaX; 740 } 741 742 /** 743 * The distance traveled in the Y-coordinate computed by the last call 744 * to {@link #computeScrollDelta()}. 745 */ 746 public int getDeltaY() { 747 return mDeltaY; 748 } 749 } 750 751 /** 752 * Implementation of {@link AutoScrollHelper} that knows how to scroll 753 * generic {@link AbsListView}s. 754 */ 755 public static class AbsListViewAutoScroller extends AutoScrollHelper { 756 private final AbsListView mTarget; 757 758 public AbsListViewAutoScroller(AbsListView target) { 759 super(target); 760 mTarget = target; 761 } 762 763 @Override 764 public boolean onScrollBy(int deltaX, int deltaY) { 765 return mTarget.scrollListBy(deltaY); 766 } 767 } 768} 769