Search code examples
androidandroid-viewpagerscroll-paging

Best way to disable ViewPager paging


I'm developing an app which have a ViewPager as a menu. Each fragment has a bunch of child views which can be ListView, ScrollView, LinearLayout, etc... One of this fragments have a settings button which toggles a settings panel (LinearLayout wrapper) with a ScrollView and some LinearLayout (buttons) and SeekBar as childs. This settings panel is animated with a slide up or down animation (when dismissed) and when it's visible I disable the ViewPager paging:

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE) {
        return true;
    }
    return super.onTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE) {
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

public boolean isPagingEnabled() {
    return pagingEnabled;
}

public void setPagingEnabled(boolean pagingEnabled) {
    this.pagingEnabled = pagingEnabled;
}

But this came with a problem, every time the panel is up all it child views wouldn't receive the OnTouchEvent and that's why I've added a GestureDetector.SimpleOnGestureListener:

protected class YScrollDetector extends GestureDetector.SimpleOnGestureListener {
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.i(C.TAG, "distanceX " + distanceX + " distanceY " + distanceY);
        return Math.abs(distanceY) < Math.abs(distanceX);
    }
}

and changed my ViewPager onInterceptTouchEvent to:

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (!pagingEnabled && event.getAction() == MotionEvent.ACTION_MOVE && (mGestureDetector != null && mGestureDetector.onTouchEvent(event))) {
        return true;
    }
    return super.onInterceptTouchEvent(event);
}

This works, the panel buttons receive their onClick, the ListView swipes, etc... but this doesn't work so perfect because Math.abs(distanceY) < Math.abs(distanceX) it's not that accurate. If I fast swipe up and down or diagonally or if I touch a button with a minor swipe the onInterceptTouchEvent will return true because mGestureDetector.onTouchEvent(event) will return true too.

After some google search I came across this:

viewPager.requestDisallowInterceptTouchEvent(true);

And I tried something like this:

myListView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        viewPager.requestDisallowInterceptTouchEvent(true);
        return false;
    }
});

And it works really well because ViewPager.onInterceptTouchEvent it's called first with MotionEvent.ACTION_DOWN and then myListView.setOnTouchListener it's called right after and disallows the remaining MotionEvent actions (MOVE and UP) and I can fast swipe up, down, sides ways, etc. The ViewPager wont page but the ListView swipes like a charm.

But a problem still remains, I've to add this requestDisallowInterceptTouchEvent(true) to all child views onTouchEvent, and it's not elegant code.

So my question is, I am on the right path? Is there anyway to avoid adding this listener to all the panel child views (of course if I have to I'll do it in the most generic way)?

Thanks for your time.


Solution

  • Disabling paging should be as simple as returning false from both onInterceptTouchEvent and onTouchEvent. You shouldn't need extra gesture detection to get around the ViewPager. Extend ViewPager like this:

    public class MyViewPager extends ViewPager {
        private boolean pagingEnabled = true;
    
        public MyViewPager(Context context) {
            super(context);
        }
    
        public MyViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public void setPagingEnabled(boolean pagingEnabled) {
            this.pagingEnabled = pagingEnabled;
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent event) {
            return pagingEnabled && super.onInterceptTouchEvent(event);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return pagingEnabled && super.onTouchEvent(event);
        }
    
    }
    

    I use the same thing (with a different name) for the same reason in one of my apps and it definitely works.