Search code examples
androidandroid-scrollviewonfling

Detect end of fling on ScrollView


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?


Solution

  • 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;
        }
    
    }