Detect end of fling on ScrollView

Christopher Perry picture Christopher Perry · Oct 2, 2013 · Viewed 7.1k times · Source

I've overridden ScrollView to pass MotionEvents to a GestureDetector to detect fling events on the ScrollView. I need to be able to detect when the scrolling stops. This doesn't coincide with the MotionEvent.ACTION_UP event because this usually happens at the start of a fling gesture, which is followed by a flurry of onScrollChanged() calls on the ScrollView.

So basically what we are dealing with here is the following events:

  1. onFling
  2. onScrollChanged, onScrollChanged, onScrollChanged, ... , onScrollChanged

There's no callback for when the onScrollChanged events are done firing. I was thinking of posting a message to the event queue using a Handler during onFling and waiting for the Runnable to execute to signal the end of the fling, unfortunately it fires after the first onScrollChanged call.

Any other ideas?

Answer

Paul Burke picture Paul Burke · Oct 3, 2013

I've combined a few of the answers from here to construct a working listener that resembles the way AbsListView does it. It's essentially what you describe, and it works well in my testing.

Note: you can simply override ScrollView.fling(int velocityY) rather than use your own GestureDetector.

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

public class CustomScrollView extends ScrollView {

    private static final int DELAY_MILLIS = 100;

    public interface OnFlingListener {
        public void onFlingStarted();
        public void onFlingStopped();
    }

    private OnFlingListener mFlingListener;
    private Runnable mScrollChecker;
    private int mPreviousPosition;

    public CustomScrollView(Context context) {
        this(context, null, 0);
    }

    public CustomScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mScrollChecker = new Runnable() {
            @Override
            public void run() {
                int position = getScrollY();
                if (mPreviousPosition - position == 0) {
                    mFlingListener.onFlingStopped();
                    removeCallbacks(mScrollChecker);
                } else {
                    mPreviousPosition = getScrollY();
                    postDelayed(mScrollChecker, DELAY_MILLIS);
                }
            }
        };
    }

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY);

        if (mFlingListener != null) {
            mFlingListener.onFlingStarted();
            post(mScrollChecker);
        }
    }

    public OnFlingListener getOnFlingListener() {
        return mFlingListener;
    }

    public void setOnFlingListener(OnFlingListener mOnFlingListener) {
        this.mFlingListener = mOnFlingListener;
    }

}