/* * Copyright (C) 2012 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 android.support.v4.widget; import android.content.Context; import android.os.Build; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.Scroller; /** * Provides access to new {@link android.widget.Scroller Scroller} APIs when available. * *
This class provides a platform version-independent mechanism for obeying the * current device's preferred scroll physics and fling behavior. It offers a subset of * the APIs from Scroller or OverScroller.
*/ public final class ScrollerCompat { private static final String TAG = "ScrollerCompat"; Object mScroller; ScrollerCompatImpl mImpl; interface ScrollerCompatImpl { Object createScroller(Context context, Interpolator interpolator); boolean isFinished(Object scroller); int getCurrX(Object scroller); int getCurrY(Object scroller); float getCurrVelocity(Object scroller); boolean computeScrollOffset(Object scroller); void startScroll(Object scroller, int startX, int startY, int dx, int dy); void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration); void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY); void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY); void abortAnimation(Object scroller); void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX); void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY); boolean isOverScrolled(Object scroller); int getFinalX(Object scroller); int getFinalY(Object scroller); boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, int minY, int maxY); } static final int CHASE_FRAME_TIME = 16; // ms per target frame static class ScrollerCompatImplBase implements ScrollerCompatImpl { @Override public Object createScroller(Context context, Interpolator interpolator) { return interpolator != null ? new Scroller(context, interpolator) : new Scroller(context); } @Override public boolean isFinished(Object scroller) { return ((Scroller) scroller).isFinished(); } @Override public int getCurrX(Object scroller) { return ((Scroller) scroller).getCurrX(); } @Override public int getCurrY(Object scroller) { return ((Scroller) scroller).getCurrY(); } @Override public float getCurrVelocity(Object scroller) { return 0; } @Override public boolean computeScrollOffset(Object scroller) { final Scroller s = (Scroller) scroller; return s.computeScrollOffset(); } @Override public void startScroll(Object scroller, int startX, int startY, int dx, int dy) { ((Scroller) scroller).startScroll(startX, startY, dx, dy); } @Override public void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration) { ((Scroller) scroller).startScroll(startX, startY, dx, dy, duration); } @Override public void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY) { ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY); } @Override public void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY) { ((Scroller) scroller).fling(startX, startY, velX, velY, minX, maxX, minY, maxY); } @Override public void abortAnimation(Object scroller) { ((Scroller) scroller).abortAnimation(); } @Override public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX) { // No-op } @Override public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { // No-op } @Override public boolean isOverScrolled(Object scroller) { // Always false return false; } @Override public int getFinalX(Object scroller) { return ((Scroller) scroller).getFinalX(); } @Override public int getFinalY(Object scroller) { return ((Scroller) scroller).getFinalY(); } @Override public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, int minY, int maxY) { return false; } } static class ScrollerCompatImplGingerbread implements ScrollerCompatImpl { @Override public Object createScroller(Context context, Interpolator interpolator) { return ScrollerCompatGingerbread.createScroller(context, interpolator); } @Override public boolean isFinished(Object scroller) { return ScrollerCompatGingerbread.isFinished(scroller); } @Override public int getCurrX(Object scroller) { return ScrollerCompatGingerbread.getCurrX(scroller); } @Override public int getCurrY(Object scroller) { return ScrollerCompatGingerbread.getCurrY(scroller); } @Override public float getCurrVelocity(Object scroller) { return 0; } @Override public boolean computeScrollOffset(Object scroller) { return ScrollerCompatGingerbread.computeScrollOffset(scroller); } @Override public void startScroll(Object scroller, int startX, int startY, int dx, int dy) { ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy); } @Override public void startScroll(Object scroller, int startX, int startY, int dx, int dy, int duration) { ScrollerCompatGingerbread.startScroll(scroller, startX, startY, dx, dy, duration); } @Override public void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY) { ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY, minX, maxX, minY, maxY); } @Override public void fling(Object scroller, int startX, int startY, int velX, int velY, int minX, int maxX, int minY, int maxY, int overX, int overY) { ScrollerCompatGingerbread.fling(scroller, startX, startY, velX, velY, minX, maxX, minY, maxY, overX, overY); } @Override public void abortAnimation(Object scroller) { ScrollerCompatGingerbread.abortAnimation(scroller); } @Override public void notifyHorizontalEdgeReached(Object scroller, int startX, int finalX, int overX) { ScrollerCompatGingerbread.notifyHorizontalEdgeReached(scroller, startX, finalX, overX); } @Override public void notifyVerticalEdgeReached(Object scroller, int startY, int finalY, int overY) { ScrollerCompatGingerbread.notifyVerticalEdgeReached(scroller, startY, finalY, overY); } @Override public boolean isOverScrolled(Object scroller) { return ScrollerCompatGingerbread.isOverScrolled(scroller); } @Override public int getFinalX(Object scroller) { return ScrollerCompatGingerbread.getFinalX(scroller); } @Override public int getFinalY(Object scroller) { return ScrollerCompatGingerbread.getFinalY(scroller); } @Override public boolean springBack(Object scroller, int startX, int startY, int minX, int maxX, int minY, int maxY) { return ScrollerCompatGingerbread.springBack(scroller, startX, startY, minX, maxX, minY, maxY); } } static class ScrollerCompatImplIcs extends ScrollerCompatImplGingerbread { @Override public float getCurrVelocity(Object scroller) { return ScrollerCompatIcs.getCurrVelocity(scroller); } } public static ScrollerCompat create(Context context) { return create(context, null); } public static ScrollerCompat create(Context context, Interpolator interpolator) { return new ScrollerCompat(Build.VERSION.SDK_INT, context, interpolator); } /** * Private constructer where API version can be provided. * Useful for unit testing. */ private ScrollerCompat(int apiVersion, Context context, Interpolator interpolator) { if (apiVersion >= 14) { // ICS mImpl = new ScrollerCompatImplIcs(); } else if (apiVersion>= 9) { // Gingerbread mImpl = new ScrollerCompatImplGingerbread(); } else { mImpl = new ScrollerCompatImplBase(); } mScroller = mImpl.createScroller(context, interpolator); } /** * Returns whether the scroller has finished scrolling. * * @return True if the scroller has finished scrolling, false otherwise. */ public boolean isFinished() { return mImpl.isFinished(mScroller); } /** * Returns the current X offset in the scroll. * * @return The new X offset as an absolute distance from the origin. */ public int getCurrX() { return mImpl.getCurrX(mScroller); } /** * Returns the current Y offset in the scroll. * * @return The new Y offset as an absolute distance from the origin. */ public int getCurrY() { return mImpl.getCurrY(mScroller); } /** * @return The final X position for the scroll in progress, if known. */ public int getFinalX() { return mImpl.getFinalX(mScroller); } /** * @return The final Y position for the scroll in progress, if known. */ public int getFinalY() { return mImpl.getFinalY(mScroller); } /** * Returns the current velocity on platform versions that support it. * *The device must support at least API level 14 (Ice Cream Sandwich). * On older platform versions this method will return 0. This method should * only be used as input for nonessential visual effects such as {@link EdgeEffectCompat}.
* * @return The original velocity less the deceleration. Result may be * negative. */ public float getCurrVelocity() { return mImpl.getCurrVelocity(mScroller); } /** * Call this when you want to know the new location. If it returns true, * the animation is not yet finished. loc will be altered to provide the * new location. */ public boolean computeScrollOffset() { return mImpl.computeScrollOffset(mScroller); } /** * Start scrolling by providing a starting point and the distance to travel. * The scroll will use the default value of 250 milliseconds for the * duration. * * @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. */ public void startScroll(int startX, int startY, int dx, int dy) { mImpl.startScroll(mScroller, startX, startY, dx, dy); } /** * 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) { mImpl.startScroll(mScroller, startX, startY, dx, dy, duration); } /** * Start scrolling based on a fling gesture. The distance travelled 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. * @param maxX Maximum X value. The scroller will not scroll past this * point. * @param minY Minimum Y value. The scroller will not scroll past this * point. * @param maxY Maximum Y value. The scroller will not scroll past this * point. */ public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { mImpl.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); } /** * Start scrolling based on a fling gesture. The distance travelled 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. * @param maxX Maximum X value. The scroller will not scroll past this * point. * @param minY Minimum Y value. The scroller will not scroll past this * point. * @param maxY Maximum Y value. The scroller will not scroll past this * point. * @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) { mImpl.fling(mScroller, startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, overX, 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 Maximum 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) { return mImpl.springBack(mScroller, startX, startY, minX, maxX, minY, maxY); } /** * Stops the animation. Aborting the animation causes the scroller to move to the final x and y * position. */ public void abortAnimation() { mImpl.abortAnimation(mScroller); } /** * 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) { mImpl.notifyHorizontalEdgeReached(mScroller, startX, finalX, overX); } /** * 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) { mImpl.notifyVerticalEdgeReached(mScroller, startY, finalY, overY); } /** * Returns whether the current Scroller is currently returning to a valid position. * Valid bounds were provided by the * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method. * * One should check this value before calling * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress * to restore a valid position will then be stopped. The caller has to take into account * the fact that the started scroll will start from an overscrolled position. * * @return true when the current position is overscrolled and in the process of * interpolating back to a valid value. */ public boolean isOverScrolled() { return mImpl.isOverScrolled(mScroller); } }