diff options
author | Alan Viverette <alanv@google.com> | 2013-08-21 13:21:45 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2013-08-21 13:21:45 -0700 |
commit | 5e66021c8a24c27c470cc6b9fe49e5653f3fa05d (patch) | |
tree | ccb9e1089a1d636b08b7f2b07513765ab1676894 /core | |
parent | 6b223c6a5be788ca28d5d911ab10650be673684b (diff) | |
download | frameworks_base-5e66021c8a24c27c470cc6b9fe49e5653f3fa05d.zip frameworks_base-5e66021c8a24c27c470cc6b9fe49e5653f3fa05d.tar.gz frameworks_base-5e66021c8a24c27c470cc6b9fe49e5653f3fa05d.tar.bz2 |
Add auto-scrolling in ListPopupWindow drag-to-open mode
BUG: 9437139
Change-Id: I836c60b48b31d0a5cc32eef903da9dc0b9b9d8a5
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/widget/ListPopupWindow.java | 18 | ||||
-rw-r--r-- | core/java/com/android/internal/widget/AutoScrollHelper.java | 768 |
2 files changed, 784 insertions, 2 deletions
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 8919248..7885a64 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -38,6 +38,8 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AccelerateDecelerateInterpolator; +import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller; + import java.util.Locale; /** @@ -1276,8 +1278,6 @@ public class ListPopupWindow { * passed to the drop down in this mode; the list only looks focused.</p> */ private static class DropDownListView extends ListView { - private static final String TAG = ListPopupWindow.TAG + ".DropDownListView"; - /** Duration in milliseconds of the drag-to-open click animation. */ private static final long CLICK_ANIM_DURATION = 150; @@ -1339,6 +1339,9 @@ public class ListPopupWindow { /** Current drag-to-open click animation, if any. */ private Animator mClickAnimation; + /** Helper for drag-to-open auto scrolling. */ + private AbsListViewAutoScroller mScrollHelper; + /** * <p>Creates a new list view wrapper.</p> * @@ -1399,6 +1402,17 @@ public class ListPopupWindow { clearPressedItem(); } + // Manage automatic scrolling. + if (handledEvent) { + if (mScrollHelper == null) { + mScrollHelper = new AbsListViewAutoScroller(this); + } + mScrollHelper.setEnabled(true); + mScrollHelper.onTouch(this, event); + } else if (mScrollHelper != null) { + mScrollHelper.setEnabled(false); + } + return handledEvent; } diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java new file mode 100644 index 0000000..f728e6a --- /dev/null +++ b/core/java/com/android/internal/widget/AutoScrollHelper.java @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.content.res.Resources; +import android.os.SystemClock; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.AbsListView; + +/** + * AutoScrollHelper is a utility class for adding automatic edge-triggered + * scrolling to Views. + * <p> + * <b>Note:</b> Implementing classes are responsible for overriding the + * {@link #onScrollBy} method to scroll the target view. See + * {@link AbsListViewAutoScroller} for an {@link android.widget.AbsListView} + * -specific implementation. + * <p> + * <h1>Activation</h1> Automatic scrolling starts when the user touches within + * an activation area. By default, activation areas are defined as the top, + * left, right, and bottom 20% of the host view's total area. Touching within + * the top activation area scrolls up, left scrolls to the left, and so on. + * <p> + * As the user touches closer to the extreme edge of the activation area, + * scrolling accelerates up to a maximum velocity. When using the default edge + * type, {@link #EDGE_TYPE_INSIDE_EXTEND}, moving outside of the view bounds + * will scroll at the maximum velocity. + * <p> + * The following activation properties may be configured: + * <ul> + * <li>Delay after entering activation area before auto-scrolling begins, see + * {@link #setActivationDelay}. Default value is + * {@link ViewConfiguration#getTapTimeout()} to avoid conflicting with taps. + * <li>Location of activation areas, see {@link #setEdgeType}. Default value is + * {@link #EDGE_TYPE_INSIDE_EXTEND}. + * <li>Size of activation areas relative to view size, see + * {@link #setRelativeEdges}. Default value is 20% for both vertical and + * horizontal edges. + * <li>Maximum size used to constrain relative size, see + * {@link #setMaximumEdges}. Default value is {@link #NO_MAX}. + * </ul> + * <h1>Scrolling</h1> When automatic scrolling is active, the helper will + * repeatedly call {@link #onScrollBy} to apply new scrolling offsets. + * <p> + * The following scrolling properties may be configured: + * <ul> + * <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default + * value is 2.5 seconds. + * <li>Target velocity relative to view size, see {@link #setRelativeVelocity}. + * Default value is 100% per second for both vertical and horizontal. + * <li>Minimum velocity used to constrain relative velocity, see + * {@link #setMinimumVelocity}. When set, scrolling will accelerate to the + * larger of either this value or the relative target value. Default value is + * approximately 5 centimeters or 315 dips per second. + * <li>Maximum velocity used to constrain relative velocity, see + * {@link #setMaximumVelocity}. Default value is approximately 25 centimeters or + * 1575 dips per second. + * </ul> + */ +public abstract class AutoScrollHelper implements View.OnTouchListener { + /** + * Constant passed to {@link #setRelativeEdges} or + * {@link #setRelativeVelocity}. Using this value ensures that the computed + * relative value is ignored and the absolute maximum value is always used. + */ + public static final float RELATIVE_UNSPECIFIED = 0; + + /** + * Constant passed to {@link #setMaximumEdges}, {@link #setMaximumVelocity}, + * or {@link #setMinimumVelocity}. Using this value ensures that the + * computed relative value is always used without constraining to a + * particular minimum or maximum value. + */ + public static final float NO_MAX = Float.MAX_VALUE; + + /** + * Constant passed to {@link #setMaximumEdges}, or + * {@link #setMaximumVelocity}, or {@link #setMinimumVelocity}. Using this + * value ensures that the computed relative value is always used without + * constraining to a particular minimum or maximum value. + */ + public static final float NO_MIN = 0; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending inward. Moving outside the view bounds will stop scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_INSIDE = 0; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending inward. After activation begins, moving outside the view + * bounds will continue scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_INSIDE_EXTEND = 1; + + /** + * Edge type that specifies an activation area starting at the view bounds + * and extending outward. Moving inside the view bounds will stop scrolling. + * + * @see #setEdgeType + */ + public static final int EDGE_TYPE_OUTSIDE = 2; + + private static final int HORIZONTAL = 0; + private static final int VERTICAL = 1; + + /** Scroller used to control acceleration toward maximum velocity. */ + private final ClampedScroller mScroller = new ClampedScroller(); + + /** Interpolator used to scale velocity with touch position. */ + private final Interpolator mEdgeInterpolator = new AccelerateInterpolator(); + + /** The view to auto-scroll. Might not be the source of touch events. */ + private final View mTarget; + + /** Runnable used to animate scrolling. */ + private Runnable mRunnable; + + /** Edge insets used to activate auto-scrolling. */ + private float[] mRelativeEdges = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; + + /** Clamping values for edge insets used to activate auto-scrolling. */ + private float[] mMaximumEdges = new float[] { NO_MAX, NO_MAX }; + + /** The type of edge being used. */ + private int mEdgeType; + + /** Delay after entering an activation edge before auto-scrolling begins. */ + private int mActivationDelay; + + /** Relative scrolling velocity at maximum edge distance. */ + private float[] mRelativeVelocity = new float[] { RELATIVE_UNSPECIFIED, RELATIVE_UNSPECIFIED }; + + /** Clamping values used for scrolling velocity. */ + private float[] mMinimumVelocity = new float[] { NO_MIN, NO_MIN }; + + /** Clamping values used for scrolling velocity. */ + private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX }; + + /** Whether to start activation immediately. */ + private boolean mSkipDelay; + + /** Whether to reset the scroller start time on the next animation. */ + private boolean mResetScroller; + + /** Whether the auto-scroller is active. */ + private boolean mActive; + + /** Whether the auto-scroller is scrolling. */ + private boolean mScrolling; + + /** Whether the auto-scroller is enabled. */ + private boolean mEnabled; + + /** Whether the auto-scroller consumes events when scrolling. */ + private boolean mExclusiveEnabled; + + /** Down time of the most recent down touch event. */ + private long mDownTime; + + // Default values. + private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND; + private static final int DEFAULT_MINIMUM_VELOCITY_DIPS = 315; + private static final int DEFAULT_MAXIMUM_VELOCITY_DIPS = 1575; + private static final float DEFAULT_MAXIMUM_EDGE = NO_MAX; + private static final float DEFAULT_RELATIVE_EDGE = 0.2f; + private static final float DEFAULT_RELATIVE_VELOCITY = 1f; + private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout(); + private static final int DEFAULT_RAMP_UP_DURATION = 2500; + // TODO: RAMP_DOWN_DURATION of 500ms? + + /** + * Creates a new helper for scrolling the specified target view. + * <p> + * The resulting helper may be configured by chaining setter calls and + * should be set as a touch listener on the target view. + * <p> + * By default, the helper is disabled and will not respond to touch events + * until it is enabled using {@link #setEnabled}. + * + * @param target The view to automatically scroll. + */ + public AutoScrollHelper(View target) { + mTarget = target; + + final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); + final int maxVelocity = (int) (DEFAULT_MAXIMUM_VELOCITY_DIPS * metrics.density + 0.5f); + final int minVelocity = (int) (DEFAULT_MINIMUM_VELOCITY_DIPS * metrics.density + 0.5f); + setMaximumVelocity(maxVelocity, maxVelocity); + setMinimumVelocity(minVelocity, minVelocity); + + setEdgeType(DEFAULT_EDGE_TYPE); + setMaximumEdges(DEFAULT_MAXIMUM_EDGE, DEFAULT_MAXIMUM_EDGE); + setRelativeEdges(DEFAULT_RELATIVE_EDGE, DEFAULT_RELATIVE_EDGE); + setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY); + setActivationDelay(DEFAULT_ACTIVATION_DELAY); + setRampUpDuration(DEFAULT_RAMP_UP_DURATION); + + mEnabled = true; + } + + /** + * Sets whether the scroll helper is enabled and should respond to touch + * events. + * + * @param enabled Whether the scroll helper is enabled. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setEnabled(boolean enabled) { + if (!enabled) { + stop(true); + } + + mEnabled = enabled; + return this; + } + + /** + * @return True if this helper is enabled and responding to touch events. + */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Enables or disables exclusive handling of touch events during scrolling. + * By default, exclusive handling is disabled and the target view receives + * all touch events. + * <p> + * When enabled, {@link #onTouch} will return true if the helper is + * currently scrolling and false otherwise. + * + * @param enabled True to exclusively handle touch events during scrolling, + * false to allow the target view to receive all touch events. + * @see #isExclusiveEnabled() + * @see #onTouch(View, MotionEvent) + */ + public void setExclusiveEnabled(boolean enabled) { + mExclusiveEnabled = enabled; + } + + /** + * Indicates whether the scroll helper handles touch events exclusively + * during scrolling. + * + * @return True if exclusive handling of touch events during scrolling is + * enabled, false otherwise. + * @see #setExclusiveEnabled(boolean) + */ + public boolean isExclusiveEnabled() { + return mExclusiveEnabled; + } + + /** + * Sets the absolute maximum scrolling velocity. + * <p> + * If relative velocity is not specified, scrolling will always reach the + * same maximum velocity. If both relative and maximum velocities are + * specified, the maximum velocity will be used to clamp the calculated + * relative velocity. + * + * @param horizontalMax The maximum horizontal scrolling velocity, or + * {@link #NO_MAX} to leave the relative value unconstrained. + * @param verticalMax The maximum vertical scrolling velocity, or + * {@link #NO_MAX} to leave the relative value unconstrained. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMaximumVelocity(float horizontalMax, float verticalMax) { + mMaximumVelocity[HORIZONTAL] = horizontalMax / 1000f; + mMaximumVelocity[VERTICAL] = verticalMax / 1000f; + return this; + } + + /** + * Sets the absolute minimum scrolling velocity. + * <p> + * If both relative and minimum velocities are specified, the minimum + * velocity will be used to clamp the calculated relative velocity. + * + * @param horizontalMin The minimum horizontal scrolling velocity, or + * {@link #NO_MIN} to leave the relative value unconstrained. + * @param verticalMin The minimum vertical scrolling velocity, or + * {@link #NO_MIN} to leave the relative value unconstrained. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMinimumVelocity(float horizontalMin, float verticalMin) { + mMinimumVelocity[HORIZONTAL] = horizontalMin / 1000f; + mMinimumVelocity[VERTICAL] = verticalMin / 1000f; + return this; + } + + /** + * Sets the target scrolling velocity relative to the host view's + * dimensions. + * <p> + * If both relative and maximum velocities are specified, the maximum + * velocity will be used to clamp the calculated relative velocity. + * + * @param horizontal The target horizontal velocity as a fraction of the + * host view width per second, or {@link #RELATIVE_UNSPECIFIED} + * to ignore. + * @param vertical The target vertical velocity as a fraction of the host + * view height per second, or {@link #RELATIVE_UNSPECIFIED} to + * ignore. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRelativeVelocity(float horizontal, float vertical) { + mRelativeVelocity[HORIZONTAL] = horizontal / 1000f; + mRelativeVelocity[VERTICAL] = vertical / 1000f; + return this; + } + + /** + * Sets the activation edge type, one of: + * <ul> + * <li>{@link #EDGE_TYPE_INSIDE} for edges that respond to touches inside + * the bounds of the host view. If touch moves outside the bounds, scrolling + * will stop. + * <li>{@link #EDGE_TYPE_INSIDE_EXTEND} for inside edges that continued to + * scroll when touch moves outside the bounds of the host view. + * <li>{@link #EDGE_TYPE_OUTSIDE} for edges that only respond to touches + * that move outside the bounds of the host view. + * </ul> + * + * @param type The type of edge to use. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setEdgeType(int type) { + mEdgeType = type; + return this; + } + + /** + * Sets the activation edge size relative to the host view's dimensions. + * <p> + * If both relative and maximum edges are specified, the maximum edge will + * be used to constrain the calculated relative edge size. + * + * @param horizontal The horizontal edge size as a fraction of the host view + * width, or {@link #RELATIVE_UNSPECIFIED} to always use the + * maximum value. + * @param vertical The vertical edge size as a fraction of the host view + * height, or {@link #RELATIVE_UNSPECIFIED} to always use the + * maximum value. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRelativeEdges(float horizontal, float vertical) { + mRelativeEdges[HORIZONTAL] = horizontal; + mRelativeEdges[VERTICAL] = vertical; + return this; + } + + /** + * Sets the absolute maximum edge size. + * <p> + * If relative edge size is not specified, activation edges will always be + * the maximum edge size. If both relative and maximum edges are specified, + * the maximum edge will be used to constrain the calculated relative edge + * size. + * + * @param horizontalMax The maximum horizontal edge size in pixels, or + * {@link #NO_MAX} to use the unconstrained calculated relative + * value. + * @param verticalMax The maximum vertical edge size in pixels, or + * {@link #NO_MAX} to use the unconstrained calculated relative + * value. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setMaximumEdges(float horizontalMax, float verticalMax) { + mMaximumEdges[HORIZONTAL] = horizontalMax; + mMaximumEdges[VERTICAL] = verticalMax; + return this; + } + + /** + * Sets the delay after entering an activation edge before activation of + * auto-scrolling. By default, the activation delay is set to + * {@link ViewConfiguration#getTapTimeout()}. + * <p> + * Specifying a delay of zero will start auto-scrolling immediately after + * the touch position enters an activation edge. + * + * @param delayMillis The activation delay in milliseconds. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setActivationDelay(int delayMillis) { + mActivationDelay = delayMillis; + return this; + } + + /** + * Sets the amount of time after activation of auto-scrolling that is takes + * to reach target velocity for the current touch position. + * <p> + * Specifying a duration greater than zero prevents sudden jumps in + * velocity. + * + * @param durationMillis The ramp-up duration in milliseconds. + * @return The scroll helper, which may used to chain setter calls. + */ + public AutoScrollHelper setRampUpDuration(int durationMillis) { + mScroller.setDuration(durationMillis); + return this; + } + + /** + * Handles touch events by activating automatic scrolling, adjusting scroll + * velocity, or stopping. + * <p> + * If {@link #isExclusiveEnabled()} is false, always returns false so that + * the host view may handle touch events. Otherwise, returns true when + * automatic scrolling is active and false otherwise. + */ + @Override + public boolean onTouch(View v, MotionEvent event) { + if (!mEnabled) { + return false; + } + + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownTime = event.getDownTime(); + case MotionEvent.ACTION_MOVE: + final float xValue = getEdgeValue(mRelativeEdges[HORIZONTAL], v.getWidth(), + mMaximumEdges[HORIZONTAL], event.getX()); + final float yValue = getEdgeValue(mRelativeEdges[VERTICAL], v.getHeight(), + mMaximumEdges[VERTICAL], event.getY()); + final float maxVelX = constrain(mRelativeVelocity[HORIZONTAL] * mTarget.getWidth(), + mMinimumVelocity[HORIZONTAL], mMaximumVelocity[HORIZONTAL]); + final float maxVelY = constrain(mRelativeVelocity[VERTICAL] * mTarget.getHeight(), + mMinimumVelocity[VERTICAL], mMaximumVelocity[VERTICAL]); + mScroller.setTargetVelocity(xValue * maxVelX, yValue * maxVelY); + + if ((xValue != 0 || yValue != 0) && !mActive) { + mActive = true; + mResetScroller = true; + if (mRunnable == null) { + mRunnable = new AutoScrollRunnable(); + } + if (mSkipDelay) { + mTarget.postOnAnimation(mRunnable); + } else { + mSkipDelay = true; + mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay); + } + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + stop(true); + break; + } + + return mExclusiveEnabled && mScrolling; + } + + /** + * Override this method to scroll the target view by the specified number + * of pixels. + * <p> + * Returns whether the target view was able to scroll the requested amount. + * + * @param deltaX The amount to scroll in the X direction, in pixels. + * @param deltaY The amount to scroll in the Y direction, in pixels. + * @return true if the target view was able to scroll the requested amount. + */ + public abstract boolean onScrollBy(int deltaX, int deltaY); + + /** + * Returns the interpolated position of a touch point relative to an edge + * defined by its relative inset, its maximum absolute inset, and the edge + * interpolator. + * + * @param relativeValue The size of the inset relative to the total size. + * @param size Total size. + * @param maxValue The maximum size of the inset, used to clamp (relative * + * total). + * @param current Touch position within within the total size. + * @return Interpolated value of the touch position within the edge. + */ + private float getEdgeValue(float relativeValue, float size, float maxValue, float current) { + // For now, leading and trailing edges are always the same size. + final float edgeSize = constrain(relativeValue * size, NO_MIN, maxValue); + final float valueLeading = constrainEdgeValue(current, edgeSize); + final float valueTrailing = constrainEdgeValue(size - current, edgeSize); + final float value = (valueTrailing - valueLeading); + final float interpolated; + if (value < 0) { + interpolated = -mEdgeInterpolator.getInterpolation(-value); + } else if (value > 0) { + interpolated = mEdgeInterpolator.getInterpolation(value); + } else { + return 0; + } + + return constrain(interpolated, -1, 1); + } + + private float constrainEdgeValue(float current, float leading) { + if (leading == 0) { + return 0; + } + + switch (mEdgeType) { + case EDGE_TYPE_INSIDE: + case EDGE_TYPE_INSIDE_EXTEND: + if (current < leading) { + if (current > 0) { + // Movement up to the edge is scaled. + return 1f - current / leading; + } else if (mActive && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) { + // Movement beyond the edge is always maximum. + return 1f; + } + } + break; + case EDGE_TYPE_OUTSIDE: + if (current < 0) { + // Movement beyond the edge is scaled. + return current / -leading; + } + break; + } + + return 0; + } + + private static float constrain(float value, float min, float max) { + if (value > max) { + return max; + } else if (value < min) { + return min; + } else { + return value; + } + } + + /** + * Stops auto-scrolling immediately, optionally reseting the auto-scrolling + * delay. + * + * @param reset Whether to reset the auto-scrolling delay. + */ + private void stop(boolean reset) { + mActive = false; + mScrolling = false; + mSkipDelay = !reset; + + if (mRunnable != null) { + mTarget.removeCallbacks(mRunnable); + } + } + + /** + * Sends a {@link MotionEvent#ACTION_CANCEL} event to the target view, + * canceling any ongoing touch events. + */ + private void cancelTargetTouch() { + final MotionEvent cancel = MotionEvent.obtain( + mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0); + cancel.setAction(MotionEvent.ACTION_CANCEL); + mTarget.onTouchEvent(cancel); + cancel.recycle(); + } + + private class AutoScrollRunnable implements Runnable { + @Override + public void run() { + if (!mActive) { + return; + } + + if (mResetScroller) { + mResetScroller = false; + mScroller.start(); + } + + final View target = mTarget; + final ClampedScroller scroller = mScroller; + scroller.computeScrollDelta(); + + final int deltaX = scroller.getDeltaX(); + final int deltaY = scroller.getDeltaY(); + if ((deltaX != 0 || deltaY != 0 || !scroller.isFinished()) + && onScrollBy(deltaX, deltaY)) { + // Update whether we're actively scrolling. + final boolean scrolling = (deltaX != 0 || deltaY != 0); + if (mScrolling != scrolling) { + mScrolling = scrolling; + + // If we just started actively scrolling, make sure any down + // or move events send to the target view are canceled. + if (mExclusiveEnabled && scrolling) { + cancelTargetTouch(); + } + } + + // Keep going until the scroller has permanently stopped or the + // view can't scroll any more. If the user moves their finger + // again, we'll repost the animation. + target.postOnAnimation(this); + } else { + stop(false); + } + } + } + + /** + * Scroller whose velocity follows the curve of an {@link Interpolator} and + * is clamped to the interpolated 0f value before starting and the + * interpolated 1f value after a specified duration. + */ + private static class ClampedScroller { + private final Interpolator mInterpolator = new AccelerateInterpolator(); + + private int mDuration; + private float mTargetVelocityX; + private float mTargetVelocityY; + + private long mStartTime; + private long mDeltaTime; + private int mDeltaX; + private int mDeltaY; + + /** + * Creates a new ramp-up scroller that reaches full velocity after a + * specified duration. + */ + public ClampedScroller() { + reset(); + } + + public void setDuration(int durationMillis) { + mDuration = durationMillis; + } + + /** + * Starts the scroller at the current animation time. + */ + public void start() { + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDeltaTime = mStartTime; + } + + /** + * Returns whether the scroller is finished, which means that its + * acceleration is zero. + * + * @return Whether the scroller is finished. + */ + public boolean isFinished() { + if (mTargetVelocityX == 0 && mTargetVelocityY == 0) { + return true; + } + final long currentTime = AnimationUtils.currentAnimationTimeMillis(); + final long elapsedSinceStart = currentTime - mStartTime; + return elapsedSinceStart > mDuration; + } + + /** + * Stops the scroller and resets its values. + */ + public void reset() { + mStartTime = -1; + mDeltaTime = -1; + mDeltaX = 0; + mDeltaY = 0; + } + + /** + * Computes the current scroll deltas. This usually only be called after + * starting the scroller with {@link #start()}. + * + * @see #getDeltaX() + * @see #getDeltaY() + */ + public void computeScrollDelta() { + final long currentTime = AnimationUtils.currentAnimationTimeMillis(); + final long elapsedSinceStart = currentTime - mStartTime; + final float value; + if (mStartTime < 0) { + value = 0f; + } else if (elapsedSinceStart < mDuration) { + value = (float) elapsedSinceStart / mDuration; + } else { + value = 1f; + } + + final float scale = mInterpolator.getInterpolation(value); + final long elapsedSinceDelta = currentTime - mDeltaTime; + + mDeltaTime = currentTime; + mDeltaX = (int) (elapsedSinceDelta * scale * mTargetVelocityX); + mDeltaY = (int) (elapsedSinceDelta * scale * mTargetVelocityY); + } + + /** + * Sets the target velocity for this scroller. + * + * @param x The target X velocity in pixels per millisecond. + * @param y The target Y velocity in pixels per millisecond. + */ + public void setTargetVelocity(float x, float y) { + mTargetVelocityX = x; + mTargetVelocityY = y; + } + + /** + * The distance traveled in the X-coordinate computed by the last call + * to {@link #computeScrollDelta()}. + */ + public int getDeltaX() { + return mDeltaX; + } + + /** + * The distance traveled in the Y-coordinate computed by the last call + * to {@link #computeScrollDelta()}. + */ + public int getDeltaY() { + return mDeltaY; + } + } + + /** + * Implementation of {@link AutoScrollHelper} that knows how to scroll + * generic {@link AbsListView}s. + */ + public static class AbsListViewAutoScroller extends AutoScrollHelper { + private final AbsListView mTarget; + + public AbsListViewAutoScroller(AbsListView target) { + super(target); + mTarget = target; + } + + @Override + public boolean onScrollBy(int deltaX, int deltaY) { + return mTarget.scrollListBy(deltaY); + } + } +} |