/**
   * Start scrolling based on a fling gesture. The distance traveled will depend on the initial
   * velocity of the fling.
   *
   * @param startX Starting point of the scroll (X)
   * @param startY Starting point of the scroll (Y)
   * @param velocityX Initial velocity of the fling (X) measured in pixels per second.
   * @param velocityY Initial velocity of the fling (Y) measured in pixels per second
   * @param minX Minimum X value. The scroller will not scroll past this point unless overX > 0. If
   *     overfling is allowed, it will use minX as a springback boundary.
   * @param maxX Maximum X value. The scroller will not scroll past this point unless overX > 0. If
   *     overfling is allowed, it will use maxX as a springback boundary.
   * @param minY Minimum Y value. The scroller will not scroll past this point unless overY > 0. If
   *     overfling is allowed, it will use minY as a springback boundary.
   * @param maxY Maximum Y value. The scroller will not scroll past this point unless overY > 0. If
   *     overfling is allowed, it will use maxY as a springback boundary.
   * @param overX Overfling range. If > 0, horizontal overfling in either direction will be
   *     possible.
   * @param overY Overfling range. If > 0, vertical overfling in either direction will be possible.
   */
  public void fling(
      int startX,
      int startY,
      int velocityX,
      int velocityY,
      int minX,
      int maxX,
      int minY,
      int maxY,
      int overX,
      int overY) {
    // Continue a scroll or fling in progress
    if (mFlywheel && !isFinished()) {
      float oldVelocityX = mScrollerX.mCurrVelocity;
      float oldVelocityY = mScrollerY.mCurrVelocity;
      if (Math.signum(velocityX) == Math.signum(oldVelocityX)
          && Math.signum(velocityY) == Math.signum(oldVelocityY)) {
        velocityX += oldVelocityX;
        velocityY += oldVelocityY;
      }
    }

    mMode = FLING_MODE;
    mScrollerX.fling(startX, velocityX, minX, maxX, overX);
    mScrollerY.fling(startY, velocityY, minY, maxY, overY);
  }
  /**
   * Call this when you want to 'spring back' into a valid coordinate range.
   *
   * @param startX Starting X coordinate
   * @param startY Starting Y coordinate
   * @param minX Minimum valid X value
   * @param maxX Maximum valid X value
   * @param minY Minimum valid Y value
   * @param maxY Minimum valid Y value
   * @return true if a springback was initiated, false if startX and startY were already within the
   *     valid range.
   */
  public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
    mMode = FLING_MODE;

    // Make sure both methods are called.
    final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
    final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
    return spingbackX || spingbackY;
  }
  /**
   * Call this when you want to know the new location. If it returns true, the animation is not yet
   * finished.
   */
  public boolean computeScrollOffset() {
    if (isFinished()) {
      return false;
    }

    switch (mMode) {
      case SCROLL_MODE:
        long time = AnimationUtils.currentAnimationTimeMillis();
        // Any scroller can be used for time, since they were started
        // together in scroll mode. We use X here.
        final long elapsedTime = time - mScrollerX.mStartTime;

        final int duration = mScrollerX.mDuration;
        if (elapsedTime < duration) {
          final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
          mScrollerX.updateScroll(q);
          mScrollerY.updateScroll(q);
        } else {
          abortAnimation();
        }
        break;

      case FLING_MODE:
        if (!mScrollerX.mFinished) {
          if (!mScrollerX.update()) {
            if (!mScrollerX.continueWhenFinished()) {
              mScrollerX.finish();
            }
          }
        }

        if (!mScrollerY.mFinished) {
          if (!mScrollerY.update()) {
            if (!mScrollerY.continueWhenFinished()) {
              mScrollerY.finish();
            }
          }
        }

        break;
    }

    return true;
  }
 /**
  * Stops the animation. Contrary to {@link #forceFinished(boolean)}, aborting the animating causes
  * the scroller to move to the final x and y positions.
  *
  * @see #forceFinished(boolean)
  */
 public void abortAnimation() {
   mScrollerX.finish();
   mScrollerY.finish();
 }
 /**
  * Notify the scroller that we've reached a vertical boundary. Normally the information to handle
  * this will already be known when the animation is started, such as in a call to one of the fling
  * functions. However there are cases where this cannot be known in advance. This function will
  * animate a parabolic motion from startY to finalY.
  *
  * @param startY Starting/current Y position
  * @param finalY Desired final Y position
  * @param overY Magnitude of overscroll allowed. This should be the maximum desired distance from
  *     finalY. Absolute value - must be positive.
  */
 public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
   mScrollerY.notifyEdgeReached(startY, finalY, overY);
 }
 /**
  * Notify the scroller that we've reached a horizontal boundary. Normally the information to
  * handle this will already be known when the animation is started, such as in a call to one of
  * the fling functions. However there are cases where this cannot be known in advance. This
  * function will transition the current motion and animate from startX to finalX as appropriate.
  *
  * @param startX Starting/current X position
  * @param finalX Desired final X position
  * @param overX Magnitude of overscroll allowed. This should be the maximum desired distance from
  *     finalX. Absolute value - must be positive.
  */
 public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
   mScrollerX.notifyEdgeReached(startX, finalX, overX);
 }
 /**
  * Start scrolling by providing a starting point and the distance to travel.
  *
  * @param startX Starting horizontal scroll offset in pixels. Positive numbers will scroll the
  *     content to the left.
  * @param startY Starting vertical scroll offset in pixels. Positive numbers will scroll the
  *     content up.
  * @param dx Horizontal distance to travel. Positive numbers will scroll the content to the left.
  * @param dy Vertical distance to travel. Positive numbers will scroll the content up.
  * @param duration Duration of the scroll in milliseconds.
  */
 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
   mMode = SCROLL_MODE;
   mScrollerX.startScroll(startX, dx, duration);
   mScrollerY.startScroll(startY, dy, duration);
 }
 /**
  * Sets the final position (Y) for this scroller.
  *
  * @param newY The new Y offset as an absolute distance from the origin.
  * @see #extendDuration(int)
  * @see #setFinalX(int)
  * @hide Pending removal once nothing depends on it
  * @deprecated OverScroller's final position may change during an animation. Instead of setting a
  *     new final position and extending the duration of an existing scroll, use startScroll to
  *     begin a new animation.
  */
 @Deprecated
 public void setFinalY(int newY) {
   mScrollerY.setFinalPosition(newY);
 }
 /**
  * Sets the final position (X) for this scroller.
  *
  * @param newX The new X offset as an absolute distance from the origin.
  * @see #extendDuration(int)
  * @see #setFinalY(int)
  * @hide Pending removal once nothing depends on it
  * @deprecated OverScroller's final position may change during an animation. Instead of setting a
  *     new final position and extending the duration of an existing scroll, use startScroll to
  *     begin a new animation.
  */
 @Deprecated
 public void setFinalX(int newX) {
   mScrollerX.setFinalPosition(newX);
 }
 /**
  * Extend the scroll animation. This allows a running animation to scroll further and longer, when
  * used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
  *
  * @param extend Additional time to scroll in milliseconds.
  * @see #setFinalX(int)
  * @see #setFinalY(int)
  * @hide Pending removal once nothing depends on it
  * @deprecated OverScrollers don't necessarily have a fixed duration. Instead of setting a new
  *     final position and extending the duration of an existing scroll, use startScroll to begin a
  *     new animation.
  */
 @Deprecated
 public void extendDuration(int extend) {
   mScrollerX.extendDuration(extend);
   mScrollerY.extendDuration(extend);
 }
 /**
  * Force the finished field to a particular value. Contrary to {@link #abortAnimation()}, forcing
  * the animation to finished does NOT cause the scroller to move to the final x and y position.
  *
  * @param finished The new finished value.
  */
 public final void forceFinished(boolean finished) {
   mScrollerX.mFinished = mScrollerY.mFinished = finished;
 }
 /**
  * The amount of friction applied to flings. The default value is {@link
  * ViewConfiguration#getScrollFriction}.
  *
  * @param friction A scalar dimension-less value representing the coefficient of friction.
  */
 public final void setFriction(float friction) {
   mScrollerX.setFriction(friction);
   mScrollerY.setFriction(friction);
 }