Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17dfce15 authored by Adam Powell's avatar Adam Powell
Browse files

Added OverScroller and overscroll effects for ScrollView and HorizontalScrollView.

parent 8fdd45e1
Loading
Loading
Loading
Loading
+19 −33
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ public class HorizontalScrollView extends FrameLayout {
    private long mLastScroll;

    private final Rect mTempRect = new Rect();
    private Scroller mScroller;
    private OverScroller mScroller;

    /**
     * Flag to indicate that we are moving focus ourselves. This is so the
@@ -177,7 +177,7 @@ public class HorizontalScrollView extends FrameLayout {


    private void initScrollView() {
        mScroller = new Scroller(getContext());
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
@@ -380,11 +380,6 @@ public class HorizontalScrollView extends FrameLayout {
            return true;
        }

        if (!canScroll()) {
            mIsBeingDragged = false;
            return false;
        }

        final float x = ev.getX();

        switch (action) {
@@ -440,10 +435,6 @@ public class HorizontalScrollView extends FrameLayout {
            return false;
        }

        if (!canScroll()) {
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
@@ -470,25 +461,23 @@ public class HorizontalScrollView extends FrameLayout {
                final int deltaX = (int) (mLastMotionX - x);
                mLastMotionX = x;

                if (deltaX < 0) {
                    if (mScrollX > 0) {
                        scrollBy(deltaX, 0);
                    }
                } else if (deltaX > 0) {
                    final int rightEdge = getWidth() - mPaddingRight;
                    final int availableToScroll = getChildAt(0).getRight() - mScrollX - rightEdge;
                    if (availableToScroll > 0) {
                        scrollBy(Math.min(availableToScroll, deltaX), 0);
                    }
                }
                super.scrollTo(mScrollX + deltaX, mScrollY);
                break;
            case MotionEvent.ACTION_UP:
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getXVelocity();

                if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
                if (getChildCount() > 0) {
                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        fling(-initialVelocity);
                    } else {
                        final int right = Math.max(0, getChildAt(0).getHeight() - 
                                (getHeight() - mPaddingRight - mPaddingLeft));
                        if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
                            invalidate();
                        }
                    }
                }

                if (mVelocityTracker != null) {
@@ -913,14 +902,10 @@ public class HorizontalScrollView extends FrameLayout {
            int oldY = mScrollY;
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
                mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
            } else {

            mScrollX = x;
            mScrollY = y;
            }

            if (oldX != mScrollX || oldY != mScrollY) {
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            }
@@ -1156,7 +1141,8 @@ public class HorizontalScrollView extends FrameLayout {
            int width = getWidth() - mPaddingRight - mPaddingLeft;
            int right = getChildAt(0).getWidth();
    
            mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0);
            mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, 
                    Math.max(0, right - width), 0, 0, width/2, 0);
    
            final boolean movingRight = velocityX > 0;
    
+354 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2006 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.widget;

import android.content.Context;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;

/**
 * This class encapsulates scrolling with the ability to overshoot the bounds
 * of a scrolling operation. This class attempts to be a drop-in replacement
 * for {@link android.widget.Scroller} in most cases.
 * 
 * @hide Pending API approval
 */
public class OverScroller {
    private static final int SPRINGBACK_DURATION = 150;
    private static final int OVERFLING_DURATION = 150;
    
    private static final int MODE_DEFAULT = 0;
    private static final int MODE_OVERFLING = 1;
    private static final int MODE_SPRINGBACK = 2;
    
    private Scroller mDefaultScroller;
    private Scroller mDecelScroller;
    private Scroller mAccelDecelScroller;
    private Scroller mCurrScroller;
    
    private int mScrollMode = MODE_DEFAULT;
    
    private int mMinimumX;
    private int mMinimumY;
    private int mMaximumX;
    private int mMaximumY;
    
    public OverScroller(Context context) {
        mDefaultScroller = new Scroller(context);
        mDecelScroller = new Scroller(context, new DecelerateInterpolator(3.f));
        mAccelDecelScroller = new Scroller(context, new AccelerateDecelerateInterpolator());
        mCurrScroller = mDefaultScroller;
    }
    
    /**
     * 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() {
        boolean inProgress = mCurrScroller.computeScrollOffset();
        
        switch (mScrollMode) {
        case MODE_OVERFLING:
            if (!inProgress) {
                // Overfling ended
                if (springback(mCurrScroller.getCurrX(), mCurrScroller.getCurrY(),
                        mMinimumX, mMaximumX, mMinimumY, mMaximumY, mAccelDecelScroller)) {
                    return mCurrScroller.computeScrollOffset();
                } else {
                    mCurrScroller = mDefaultScroller;
                    mScrollMode = MODE_DEFAULT;
                }
            }
            break;
            
        case MODE_SPRINGBACK:
            if (!inProgress) {
                mCurrScroller = mDefaultScroller;
                mScrollMode = MODE_DEFAULT;
            }
            break;
            
        case MODE_DEFAULT:
            // Fling/autoscroll - did we go off the edge?
            if (inProgress) {
                Scroller scroller = mCurrScroller;
                final int x = scroller.getCurrX();
                final int y = scroller.getCurrY();
                final int minX = mMinimumX;
                final int maxX = mMaximumX;
                final int minY = mMinimumY;
                final int maxY = mMaximumY;
                if (x < minX || x > maxX || y < minY || y > maxY) {
                    final int startx = scroller.getStartX();
                    final int starty = scroller.getStartY();
                    final int time = scroller.timePassed();
                    final float timeSecs = time / 1000.f;
                    final float xvel = ((x - startx) / timeSecs);
                    final float yvel = ((y - starty) / timeSecs);
                    
                    if ((x < minX && xvel > 0) || (y < minY && yvel > 0) ||
                            (x > maxX && xvel < 0) || (y > maxY && yvel < 0)) {
                        // If our velocity would take us back into valid areas,
                        // try to springback rather than overfling.
                        if (springback(x, y, minX, maxX, minY, maxY)) {
                            return mCurrScroller.computeScrollOffset();
                        }
                    } else {
                        overfling(x, y, xvel, yvel);
                        return mCurrScroller.computeScrollOffset();
                    }
                }
            }
            break;
        }
        
        return inProgress;
    }
    
    private void overfling(int startx, int starty, float xvel, float yvel) {
        Scroller scroller = mDecelScroller;
        final float durationSecs = (OVERFLING_DURATION / 1000.f);
        int dx = (int)(xvel * durationSecs) / 8;
        int dy = (int)(yvel * durationSecs) / 8;
        scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION);
        mCurrScroller.abortAnimation();
        mCurrScroller = scroller;
        mScrollMode = MODE_OVERFLING;
    }
    
    /**
     * 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/startY was
     *          already within the valid range.
     */
    public boolean springback(int startX, int startY, int minX, int maxX,
            int minY, int maxY) {
        return springback(startX, startY, minX, maxX, minY, maxY, mDecelScroller);
    }
    
    private boolean springback(int startX, int startY, int minX, int maxX,
            int minY, int maxY, Scroller scroller) {
        int xoff = 0;
        int yoff = 0;
        if (startX < minX) {
            xoff = minX - startX;
        } else if (startX > maxX) {
            xoff = maxX - startX;
        }
        if (startY < minY) {
            yoff = minY - startY;
        } else if (startY > maxY) {
            yoff = maxY - startY;
        }
        
        if (xoff != 0 || yoff != 0) {
            scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION);
            mCurrScroller.abortAnimation();
            mCurrScroller = scroller;
            mScrollMode = MODE_SPRINGBACK;
            return true;
        }
        
        return false;
    }

    /**
     * 
     * Returns whether the scroller has finished scrolling.
     * 
     * @return True if the scroller has finished scrolling, false otherwise.
     */
    public final boolean isFinished() {
        return mCurrScroller.isFinished();
    }

    /**
     * Returns the current X offset in the scroll. 
     * 
     * @return The new X offset as an absolute distance from the origin.
     */
    public final int getCurrX() {
        return mCurrScroller.getCurrX();
    }
    
    /**
     * Returns the current Y offset in the scroll. 
     * 
     * @return The new Y offset as an absolute distance from the origin.
     */
    public final int getCurrY() {
        return mCurrScroller.getCurrY();
    }
    
    /**
     * Stops the animation, resets any springback/overfling and completes
     * any standard flings/scrolls in progress.
     */
    public void abortAnimation() {
        mCurrScroller.abortAnimation();
        mCurrScroller = mDefaultScroller;
        mScrollMode = MODE_DEFAULT;
        mCurrScroller.abortAnimation();
    }
    
    /**
     * 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) {
        mCurrScroller.abortAnimation();
        mCurrScroller = mDefaultScroller;
        mScrollMode = MODE_DEFAULT;
        mMinimumX = Math.min(startX, startX + dx);
        mMinimumY = Math.min(startY, startY + dy);
        mMaximumX = Math.max(startX, startX + dx);
        mMaximumY = Math.max(startY, startY + dy);
        mCurrScroller.startScroll(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) {
        mCurrScroller.abortAnimation();
        mCurrScroller = mDefaultScroller;
        mScrollMode = MODE_DEFAULT;
        mMinimumX = Math.min(startX, startX + dx);
        mMinimumY = Math.min(startY, startY + dy);
        mMaximumX = Math.max(startX, startX + dx);
        mMaximumY = Math.max(startY, startY + dy);
        mCurrScroller.startScroll(startX, startY, dx, dy, duration);
    }
    
    /**
     * Returns the duration of the active scroll in progress; standard, fling,
     * springback, or overfling. Does not account for any overflings or springback
     * that may result.
     */
    public int getDuration() {
        return mCurrScroller.getDuration();
    }

    /**
     * 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) {
        this.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
    }

    /**
     * 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 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) {
        mCurrScroller = mDefaultScroller;
        mScrollMode = MODE_DEFAULT;
        mMinimumX = minX;
        mMaximumX = maxX;
        mMinimumY = minY;
        mMaximumY = maxY;
        mCurrScroller.fling(startX, startY, velocityX, velocityY, 
                minX - overX, maxX + overX, minY - overY, maxY + overY);
    }

    /**
     * Returns where the scroll will end. Valid only for "fling" scrolls.
     * 
     * @return The final X offset as an absolute distance from the origin.
     */
    public int getFinalX() {
        return mCurrScroller.getFinalX();
    }
    
    /**
     * Returns where the scroll will end. Valid only for "fling" scrolls.
     * 
     * @return The final Y offset as an absolute distance from the origin.
     */
    public int getFinalY() {
        return mCurrScroller.getFinalY();
    }
}
+21 −35
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.widget;

import com.android.internal.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -30,8 +32,6 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;

import com.android.internal.R;

import java.util.List;

/**
@@ -59,7 +59,7 @@ public class ScrollView extends FrameLayout {
    private long mLastScroll;

    private final Rect mTempRect = new Rect();
    private Scroller mScroller;
    private OverScroller mScroller;

    /**
     * Flag to indicate that we are moving focus ourselves. This is so the
@@ -173,7 +173,7 @@ public class ScrollView extends FrameLayout {


    private void initScrollView() {
        mScroller = new Scroller(getContext());
        mScroller = new OverScroller(getContext());
        setFocusable(true);
        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
        setWillNotDraw(false);
@@ -378,11 +378,6 @@ public class ScrollView extends FrameLayout {
            return true;
        }

        if (!canScroll()) {
            mIsBeingDragged = false;
            return false;
        }

        final float y = ev.getY();

        switch (action) {
@@ -438,10 +433,6 @@ public class ScrollView extends FrameLayout {
            return false;
        }

        if (!canScroll()) {
            return false;
        }

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
@@ -468,25 +459,23 @@ public class ScrollView extends FrameLayout {
                final int deltaY = (int) (mLastMotionY - y);
                mLastMotionY = y;

                if (deltaY < 0) {
                    if (mScrollY > 0) {
                        scrollBy(0, deltaY);
                    }
                } else if (deltaY > 0) {
                    final int bottomEdge = getHeight() - mPaddingBottom;
                    final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge;
                    if (availableToScroll > 0) {
                        scrollBy(0, Math.min(availableToScroll, deltaY));
                    }
                }
                super.scrollTo(mScrollX, mScrollY + deltaY);
                break;
            case MotionEvent.ACTION_UP:
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getYVelocity();

                if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
                if (getChildCount() > 0) {
                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        fling(-initialVelocity);
                    } else {
                        final int bottom = Math.max(0, getChildAt(0).getHeight() - 
                                (getHeight() - mPaddingBottom - mPaddingTop));
                        if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
                            invalidate();
                        }
                    }
                }

                if (mVelocityTracker != null) {
@@ -915,14 +904,10 @@ public class ScrollView extends FrameLayout {
            int oldY = mScrollY;
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
                mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
            } else {

            mScrollX = x;
            mScrollY = y;
            }            

            if (oldX != mScrollX || oldY != mScrollY) {
                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            }
@@ -1159,7 +1144,8 @@ public class ScrollView extends FrameLayout {
            int height = getHeight() - mPaddingBottom - mPaddingTop;
            int bottom = getChildAt(0).getHeight();
    
            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height);
            mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, 
                    Math.max(0, bottom - height), 0, height/2);
    
            final boolean movingDown = velocityY > 0;